Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Postgres FD Implementation
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Abuhujair Javed
Postgres FD Implementation
Commits
a341a96c
Commit
a341a96c
authored
Jan 19, 2005
by
Neil Conway
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor transformExpr() by creating separate functions for most of the
expression types.
parent
a73198ac
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
827 additions
and
761 deletions
+827
-761
src/backend/parser/parse_expr.c
src/backend/parser/parse_expr.c
+827
-761
No files found.
src/backend/parser/parse_expr.c
View file @
a341a96c
...
...
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.1
79 2005/01/12 17:32:36 tgl
Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.1
80 2005/01/19 23:45:24 neilc
Exp $
*
*-------------------------------------------------------------------------
*/
...
...
@@ -37,9 +37,27 @@
bool
Transform_null_equals
=
false
;
static
Node
*
transformParamRef
(
ParseState
*
pstate
,
ParamRef
*
pref
);
static
Node
*
transformAExprOp
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprAnd
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprOr
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprNot
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprOpAny
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprOpAll
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprDistinct
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprNullIf
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformAExprOf
(
ParseState
*
pstate
,
A_Expr
*
a
);
static
Node
*
transformFuncCall
(
ParseState
*
pstate
,
FuncCall
*
fn
);
static
Node
*
transformCaseExpr
(
ParseState
*
pstate
,
CaseExpr
*
c
);
static
Node
*
transformSubLink
(
ParseState
*
pstate
,
SubLink
*
sublink
);
static
Node
*
transformArrayExpr
(
ParseState
*
pstate
,
ArrayExpr
*
a
);
static
Node
*
transformRowExpr
(
ParseState
*
pstate
,
RowExpr
*
r
);
static
Node
*
transformCoalesceExpr
(
ParseState
*
pstate
,
CoalesceExpr
*
c
);
static
Node
*
transformBooleanTest
(
ParseState
*
pstate
,
BooleanTest
*
b
);
static
Node
*
transformColumnRef
(
ParseState
*
pstate
,
ColumnRef
*
cref
);
static
Node
*
transformWholeRowRef
(
ParseState
*
pstate
,
char
*
schemaname
,
char
*
relname
);
static
Node
*
transformBooleanTest
(
ParseState
*
pstate
,
BooleanTest
*
b
);
static
Node
*
transformIndirection
(
ParseState
*
pstate
,
Node
*
basenode
,
List
*
indirection
);
static
Node
*
typecast_expression
(
ParseState
*
pstate
,
Node
*
expr
,
...
...
@@ -90,65 +108,13 @@ transformExpr(ParseState *pstate, Node *expr)
switch
(
nodeTag
(
expr
))
{
case
T_ColumnRef
:
{
result
=
transformColumnRef
(
pstate
,
(
ColumnRef
*
)
expr
);
break
;
}
case
T_ParamRef
:
{
ParamRef
*
pref
=
(
ParamRef
*
)
expr
;
int
paramno
=
pref
->
number
;
ParseState
*
toppstate
;
Param
*
param
;
/*
* Find topmost ParseState, which is where paramtype info
* lives.
*/
toppstate
=
pstate
;
while
(
toppstate
->
parentParseState
!=
NULL
)
toppstate
=
toppstate
->
parentParseState
;
/* Check parameter number is in range */
if
(
paramno
<=
0
)
/* probably can't happen? */
ereport
(
ERROR
,
(
errcode
(
ERRCODE_UNDEFINED_PARAMETER
),
errmsg
(
"there is no parameter $%d"
,
paramno
)));
if
(
paramno
>
toppstate
->
p_numparams
)
{
if
(
!
toppstate
->
p_variableparams
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_UNDEFINED_PARAMETER
),
errmsg
(
"there is no parameter $%d"
,
paramno
)));
/* Okay to enlarge param array */
if
(
toppstate
->
p_paramtypes
)
toppstate
->
p_paramtypes
=
(
Oid
*
)
repalloc
(
toppstate
->
p_paramtypes
,
paramno
*
sizeof
(
Oid
));
else
toppstate
->
p_paramtypes
=
(
Oid
*
)
palloc
(
paramno
*
sizeof
(
Oid
));
/* Zero out the previously-unreferenced slots */
MemSet
(
toppstate
->
p_paramtypes
+
toppstate
->
p_numparams
,
0
,
(
paramno
-
toppstate
->
p_numparams
)
*
sizeof
(
Oid
));
toppstate
->
p_numparams
=
paramno
;
}
if
(
toppstate
->
p_variableparams
)
{
/* If not seen before, initialize to UNKNOWN type */
if
(
toppstate
->
p_paramtypes
[
paramno
-
1
]
==
InvalidOid
)
toppstate
->
p_paramtypes
[
paramno
-
1
]
=
UNKNOWNOID
;
}
param
=
makeNode
(
Param
);
param
->
paramkind
=
PARAM_NUM
;
param
->
paramid
=
(
AttrNumber
)
paramno
;
param
->
paramtype
=
toppstate
->
p_paramtypes
[
paramno
-
1
];
result
=
(
Node
*
)
param
;
case
T_ParamRef
:
result
=
transformParamRef
(
pstate
,
(
ParamRef
*
)
expr
);
break
;
}
case
T_A_Const
:
{
A_Const
*
con
=
(
A_Const
*
)
expr
;
...
...
@@ -160,6 +126,7 @@ transformExpr(ParseState *pstate, Node *expr)
con
->
typename
);
break
;
}
case
T_A_Indirection
:
{
A_Indirection
*
ind
=
(
A_Indirection
*
)
expr
;
...
...
@@ -169,6 +136,7 @@ transformExpr(ParseState *pstate, Node *expr)
ind
->
indirection
);
break
;
}
case
T_TypeCast
:
{
TypeCast
*
tc
=
(
TypeCast
*
)
expr
;
...
...
@@ -177,6 +145,7 @@ transformExpr(ParseState *pstate, Node *expr)
result
=
typecast_expression
(
pstate
,
arg
,
tc
->
typename
);
break
;
}
case
T_A_Expr
:
{
A_Expr
*
a
=
(
A_Expr
*
)
expr
;
...
...
@@ -184,449 +153,652 @@ transformExpr(ParseState *pstate, Node *expr)
switch
(
a
->
kind
)
{
case
AEXPR_OP
:
{
Node
*
lexpr
=
a
->
lexpr
;
Node
*
rexpr
=
a
->
rexpr
;
/*
* Special-case "foo = NULL" and "NULL = foo"
* for compatibility with standards-broken
* products (like Microsoft's). Turn these
* into IS NULL exprs.
*/
if
(
Transform_null_equals
&&
list_length
(
a
->
name
)
==
1
&&
strcmp
(
strVal
(
linitial
(
a
->
name
)),
"="
)
==
0
&&
(
exprIsNullConstant
(
lexpr
)
||
exprIsNullConstant
(
rexpr
)))
{
NullTest
*
n
=
makeNode
(
NullTest
);
n
->
nulltesttype
=
IS_NULL
;
if
(
exprIsNullConstant
(
lexpr
))
n
->
arg
=
(
Expr
*
)
rexpr
;
else
n
->
arg
=
(
Expr
*
)
lexpr
;
result
=
transformExpr
(
pstate
,
(
Node
*
)
n
);
}
else
if
(
lexpr
&&
IsA
(
lexpr
,
RowExpr
)
&&
rexpr
&&
IsA
(
rexpr
,
SubLink
)
&&
((
SubLink
*
)
rexpr
)
->
subLinkType
==
EXPR_SUBLINK
)
{
/*
* Convert "row op subselect" into a
* MULTIEXPR sublink. Formerly the
* grammar did this, but now that a row
* construct is allowed anywhere in
* expressions, it's easier to do it here.
*/
SubLink
*
s
=
(
SubLink
*
)
rexpr
;
s
->
subLinkType
=
MULTIEXPR_SUBLINK
;
s
->
lefthand
=
((
RowExpr
*
)
lexpr
)
->
args
;
s
->
operName
=
a
->
name
;
result
=
transformExpr
(
pstate
,
(
Node
*
)
s
);
}
else
if
(
lexpr
&&
IsA
(
lexpr
,
RowExpr
)
&&
rexpr
&&
IsA
(
rexpr
,
RowExpr
))
{
/* "row op row" */
result
=
make_row_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
else
{
/* Ordinary scalar operator */
lexpr
=
transformExpr
(
pstate
,
lexpr
);
rexpr
=
transformExpr
(
pstate
,
rexpr
);
result
=
(
Node
*
)
make_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
}
result
=
transformAExprOp
(
pstate
,
a
);
break
;
case
AEXPR_AND
:
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
lexpr
=
coerce_to_boolean
(
pstate
,
lexpr
,
"AND"
);
rexpr
=
coerce_to_boolean
(
pstate
,
rexpr
,
"AND"
);
result
=
(
Node
*
)
makeBoolExpr
(
AND_EXPR
,
list_make2
(
lexpr
,
rexpr
));
}
result
=
transformAExprAnd
(
pstate
,
a
);
break
;
case
AEXPR_OR
:
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
lexpr
=
coerce_to_boolean
(
pstate
,
lexpr
,
"OR"
);
rexpr
=
coerce_to_boolean
(
pstate
,
rexpr
,
"OR"
);
result
=
(
Node
*
)
makeBoolExpr
(
OR_EXPR
,
list_make2
(
lexpr
,
rexpr
));
}
result
=
transformAExprOr
(
pstate
,
a
);
break
;
case
AEXPR_NOT
:
{
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
rexpr
=
coerce_to_boolean
(
pstate
,
rexpr
,
"NOT"
);
result
=
(
Node
*
)
makeBoolExpr
(
NOT_EXPR
,
list_make1
(
rexpr
));
}
result
=
transformAExprNot
(
pstate
,
a
);
break
;
case
AEXPR_OP_ANY
:
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
result
=
(
Node
*
)
make_scalar_array_op
(
pstate
,
a
->
name
,
true
,
lexpr
,
rexpr
);
}
result
=
transformAExprOpAny
(
pstate
,
a
);
break
;
case
AEXPR_OP_ALL
:
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
result
=
(
Node
*
)
make_scalar_array_op
(
pstate
,
a
->
name
,
false
,
lexpr
,
rexpr
);
}
result
=
transformAExprOpAll
(
pstate
,
a
);
break
;
case
AEXPR_DISTINCT
:
{
Node
*
lexpr
=
a
->
lexpr
;
Node
*
rexpr
=
a
->
rexpr
;
if
(
lexpr
&&
IsA
(
lexpr
,
RowExpr
)
&&
rexpr
&&
IsA
(
rexpr
,
RowExpr
))
{
/* "row op row" */
result
=
make_row_distinct_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
else
{
/* Ordinary scalar operator */
lexpr
=
transformExpr
(
pstate
,
lexpr
);
rexpr
=
transformExpr
(
pstate
,
rexpr
);
result
=
(
Node
*
)
make_distinct_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
}
result
=
transformAExprDistinct
(
pstate
,
a
);
break
;
case
AEXPR_NULLIF
:
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
result
=
(
Node
*
)
make_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
if
(((
OpExpr
*
)
result
)
->
opresulttype
!=
BOOLOID
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_DATATYPE_MISMATCH
),
errmsg
(
"NULLIF requires = operator to yield boolean"
)));
/*
* We rely on NullIfExpr and OpExpr being same
* struct
*/
NodeSetTag
(
result
,
T_NullIfExpr
);
}
result
=
transformAExprNullIf
(
pstate
,
a
);
break
;
case
AEXPR_OF
:
{
/*
* Checking an expression for match to type.
* Will result in a boolean constant node.
*/
ListCell
*
telem
;
A_Const
*
n
;
Oid
ltype
,
rtype
;
bool
matched
=
FALSE
;
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
ltype
=
exprType
(
lexpr
);
foreach
(
telem
,
(
List
*
)
a
->
rexpr
)
{
rtype
=
LookupTypeName
(
lfirst
(
telem
));
matched
=
(
rtype
==
ltype
);
if
(
matched
)
result
=
transformAExprOf
(
pstate
,
a
);
break
;
default:
elog
(
ERROR
,
"unrecognized A_Expr kind: %d"
,
a
->
kind
);
}
break
;
}
/*
* Expect two forms: equals or not equals.
* Flip the sense of the result for not
* equals.
*/
if
(
strcmp
(
strVal
(
linitial
(
a
->
name
)),
"!="
)
==
0
)
matched
=
(
!
matched
);
case
T_FuncCall
:
result
=
transformFuncCall
(
pstate
,
(
FuncCall
*
)
expr
);
break
;
n
=
makeNode
(
A_Const
);
n
->
val
.
type
=
T_String
;
n
->
val
.
val
.
str
=
(
matched
?
"t"
:
"f"
);
n
->
typename
=
SystemTypeName
(
"bool"
);
case
T_SubLink
:
result
=
transformSubLink
(
pstate
,
(
SubLink
*
)
expr
);
break
;
result
=
transformExpr
(
pstate
,
(
Node
*
)
n
);
}
case
T_CaseExpr
:
result
=
transformCaseExpr
(
pstate
,
(
CaseExpr
*
)
expr
);
break
;
}
case
T_ArrayExpr
:
result
=
transformArrayExpr
(
pstate
,
(
ArrayExpr
*
)
expr
);
break
;
}
case
T_FuncCall
:
{
FuncCall
*
fn
=
(
FuncCall
*
)
expr
;
List
*
targs
;
ListCell
*
args
;
/*
* Transform the list of arguments. We use a shallow list
* copy and then transform-in-place to avoid O(N^2)
* behavior from repeated lappend's.
*
* XXX: repeated lappend() would no longer result in O(n^2)
* behavior; worth reconsidering this design?
*/
targs
=
list_copy
(
fn
->
args
);
foreach
(
args
,
targs
)
{
lfirst
(
args
)
=
transformExpr
(
pstate
,
(
Node
*
)
lfirst
(
args
));
}
result
=
ParseFuncOrColumn
(
pstate
,
fn
->
funcname
,
targs
,
fn
->
agg_star
,
fn
->
agg_distinct
,
false
);
case
T_RowExpr
:
result
=
transformRowExpr
(
pstate
,
(
RowExpr
*
)
expr
);
break
;
}
case
T_SubLink
:
{
SubLink
*
sublink
=
(
SubLink
*
)
expr
;
List
*
qtrees
;
Query
*
qtree
;
/* If we already transformed this node, do nothing */
if
(
IsA
(
sublink
->
subselect
,
Query
))
case
T_CoalesceExpr
:
result
=
transformCoalesceExpr
(
pstate
,
(
CoalesceExpr
*
)
expr
);
break
;
case
T_NullTest
:
{
NullTest
*
n
=
(
NullTest
*
)
expr
;
n
->
arg
=
(
Expr
*
)
transformExpr
(
pstate
,
(
Node
*
)
n
->
arg
);
/* the argument can be any type, so don't coerce it */
result
=
expr
;
break
;
}
pstate
->
p_hasSubLinks
=
true
;
qtrees
=
parse_sub_analyze
(
sublink
->
subselect
,
pstate
);
if
(
list_length
(
qtrees
)
!=
1
)
elog
(
ERROR
,
"bad query in sub-select"
);
qtree
=
(
Query
*
)
linitial
(
qtrees
);
if
(
qtree
->
commandType
!=
CMD_SELECT
||
qtree
->
resultRelation
!=
0
)
elog
(
ERROR
,
"bad query in sub-select"
);
sublink
->
subselect
=
(
Node
*
)
qtree
;
if
(
sublink
->
subLinkType
==
EXISTS_SUBLINK
)
case
T_BooleanTest
:
result
=
transformBooleanTest
(
pstate
,
(
BooleanTest
*
)
expr
);
break
;
/*********************************************
* Quietly accept node types that may be presented when we are
* called on an already-transformed tree.
*
* Do any other node types need to be accepted? For now we are
* taking a conservative approach, and only accepting node
* types that are demonstrably necessary to accept.
*********************************************/
case
T_Var
:
case
T_Const
:
case
T_Param
:
case
T_Aggref
:
case
T_ArrayRef
:
case
T_FuncExpr
:
case
T_OpExpr
:
case
T_DistinctExpr
:
case
T_ScalarArrayOpExpr
:
case
T_NullIfExpr
:
case
T_BoolExpr
:
case
T_FieldSelect
:
case
T_FieldStore
:
case
T_RelabelType
:
case
T_ConvertRowtypeExpr
:
case
T_CaseTestExpr
:
case
T_CoerceToDomain
:
case
T_CoerceToDomainValue
:
case
T_SetToDefault
:
{
result
=
(
Node
*
)
expr
;
break
;
}
default:
/* should not reach here */
elog
(
ERROR
,
"unrecognized node type: %d"
,
(
int
)
nodeTag
(
expr
));
break
;
}
return
result
;
}
static
Node
*
transformIndirection
(
ParseState
*
pstate
,
Node
*
basenode
,
List
*
indirection
)
{
Node
*
result
=
basenode
;
List
*
subscripts
=
NIL
;
ListCell
*
i
;
/*
* EXISTS needs no lefthand or combining operator.
* These fields should be NIL already, but make sure.
* We have to split any field-selection operations apart from
* subscripting. Adjacent A_Indices nodes have to be treated as a
* single multidimensional subscript operation.
*/
sublink
->
lefthand
=
NIL
;
sublink
->
operName
=
NIL
;
sublink
->
operOids
=
NIL
;
sublink
->
useOr
=
FALSE
;
foreach
(
i
,
indirection
)
{
Node
*
n
=
lfirst
(
i
);
if
(
IsA
(
n
,
A_Indices
))
subscripts
=
lappend
(
subscripts
,
n
);
else
{
Assert
(
IsA
(
n
,
String
));
/* process subscripts before this field selection */
if
(
subscripts
)
result
=
(
Node
*
)
transformArraySubscripts
(
pstate
,
result
,
exprType
(
result
),
InvalidOid
,
-
1
,
subscripts
,
NULL
);
subscripts
=
NIL
;
result
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
n
),
list_make1
(
result
),
false
,
false
,
true
);
}
else
if
(
sublink
->
subLinkType
==
EXPR_SUBLINK
||
sublink
->
subLinkType
==
ARRAY_SUBLINK
)
}
/* process trailing subscripts, if any */
if
(
subscripts
)
result
=
(
Node
*
)
transformArraySubscripts
(
pstate
,
result
,
exprType
(
result
),
InvalidOid
,
-
1
,
subscripts
,
NULL
);
return
result
;
}
static
Node
*
transformColumnRef
(
ParseState
*
pstate
,
ColumnRef
*
cref
)
{
int
numnames
=
list_length
(
cref
->
fields
);
Node
*
node
;
int
levels_up
;
/*----------
* The allowed syntaxes are:
*
* A First try to resolve as unqualified column name;
* if no luck, try to resolve as unqualified table name (A.*).
* A.B A is an unqualified table name; B is either a
* column or function name (trying column name first).
* A.B.C schema A, table B, col or func name C.
* A.B.C.D catalog A, schema B, table C, col or func D.
* A.* A is an unqualified table name; means whole-row value.
* A.B.* whole-row value of table B in schema A.
* A.B.C.* whole-row value of table C in schema B in catalog A.
*
* We do not need to cope with bare "*"; that will only be accepted by
* the grammar at the top level of a SELECT list, and transformTargetList
* will take care of it before it ever gets here. Also, "A.*" etc will
* be expanded by transformTargetList if they appear at SELECT top level,
* so here we are only going to see them as function or operator inputs.
*
* Currently, if a catalog name is given then it must equal the current
* database name; we check it here and then discard it.
*----------
*/
switch
(
numnames
)
{
ListCell
*
tlist_item
=
list_head
(
qtree
->
targetList
);
case
1
:
{
char
*
name
=
strVal
(
linitial
(
cref
->
fields
));
/* Try to identify as an unqualified column */
node
=
colNameToVar
(
pstate
,
name
,
false
);
if
(
node
==
NULL
)
{
/*
* Not known as a column of any range-table entry.
*
* Consider the possibility that it's VALUE in a domain
* check expression. (We handle VALUE as a name, not
* a keyword, to avoid breaking a lot of applications
* that have used VALUE as a column name in the past.)
*/
if
(
pstate
->
p_value_substitute
!=
NULL
&&
strcmp
(
name
,
"value"
)
==
0
)
{
node
=
(
Node
*
)
copyObject
(
pstate
->
p_value_substitute
);
break
;
}
/*
* Try to find the name as a relation. Note that only
* relations already entered into the rangetable will
* be recognized.
*
* This is a hack for backwards compatibility with
* PostQUEL-inspired syntax. The preferred form now
* is "rel.*".
*/
if
(
refnameRangeTblEntry
(
pstate
,
NULL
,
name
,
&
levels_up
)
!=
NULL
)
node
=
transformWholeRowRef
(
pstate
,
NULL
,
name
);
else
ereport
(
ERROR
,
(
errcode
(
ERRCODE_UNDEFINED_COLUMN
),
errmsg
(
"column
\"
%s
\"
does not exist"
,
name
)));
}
break
;
}
case
2
:
{
char
*
name1
=
strVal
(
linitial
(
cref
->
fields
));
char
*
name2
=
strVal
(
lsecond
(
cref
->
fields
));
/* Whole-row reference? */
if
(
strcmp
(
name2
,
"*"
)
==
0
)
{
node
=
transformWholeRowRef
(
pstate
,
NULL
,
name1
);
break
;
}
/* Try to identify as a once-qualified column */
node
=
qualifiedNameToVar
(
pstate
,
NULL
,
name1
,
name2
,
true
);
if
(
node
==
NULL
)
{
/*
* Not known as a column of any range-table entry, so
* try it as a function call. Here, we will create an
* implicit RTE for tables not already entered.
*/
node
=
transformWholeRowRef
(
pstate
,
NULL
,
name1
);
node
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
makeString
(
name2
)),
list_make1
(
node
),
false
,
false
,
true
);
}
break
;
}
case
3
:
{
char
*
name1
=
strVal
(
linitial
(
cref
->
fields
));
char
*
name2
=
strVal
(
lsecond
(
cref
->
fields
));
char
*
name3
=
strVal
(
lthird
(
cref
->
fields
));
/* Whole-row reference? */
if
(
strcmp
(
name3
,
"*"
)
==
0
)
{
node
=
transformWholeRowRef
(
pstate
,
name1
,
name2
);
break
;
}
/* Try to identify as a twice-qualified column */
node
=
qualifiedNameToVar
(
pstate
,
name1
,
name2
,
name3
,
true
);
if
(
node
==
NULL
)
{
/* Try it as a function call */
node
=
transformWholeRowRef
(
pstate
,
name1
,
name2
);
node
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
makeString
(
name3
)),
list_make1
(
node
),
false
,
false
,
true
);
}
break
;
}
case
4
:
{
char
*
name1
=
strVal
(
linitial
(
cref
->
fields
));
char
*
name2
=
strVal
(
lsecond
(
cref
->
fields
));
char
*
name3
=
strVal
(
lthird
(
cref
->
fields
));
char
*
name4
=
strVal
(
lfourth
(
cref
->
fields
));
/*
* We check the catalog name and then ignore it.
*/
if
(
strcmp
(
name1
,
get_database_name
(
MyDatabaseId
))
!=
0
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_FEATURE_NOT_SUPPORTED
),
errmsg
(
"cross-database references are not implemented: %s"
,
NameListToString
(
cref
->
fields
))));
/* Whole-row reference? */
if
(
strcmp
(
name4
,
"*"
)
==
0
)
{
node
=
transformWholeRowRef
(
pstate
,
name2
,
name3
);
break
;
}
/* Try to identify as a twice-qualified column */
node
=
qualifiedNameToVar
(
pstate
,
name2
,
name3
,
name4
,
true
);
if
(
node
==
NULL
)
{
/* Try it as a function call */
node
=
transformWholeRowRef
(
pstate
,
name2
,
name3
);
node
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
makeString
(
name4
)),
list_make1
(
node
),
false
,
false
,
true
);
}
break
;
}
default:
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"improper qualified name (too many dotted names): %s"
,
NameListToString
(
cref
->
fields
))));
node
=
NULL
;
/* keep compiler quiet */
break
;
}
return
node
;
}
static
Node
*
transformParamRef
(
ParseState
*
pstate
,
ParamRef
*
pref
)
{
int
paramno
=
pref
->
number
;
ParseState
*
toppstate
;
Param
*
param
;
/*
* Make sure the subselect delivers a single column
* (ignoring resjunk targets).
* Find topmost ParseState, which is where paramtype info lives.
*/
if
(
tlist_item
==
NULL
||
((
TargetEntry
*
)
lfirst
(
tlist_item
))
->
resdom
->
resjunk
)
toppstate
=
pstate
;
while
(
toppstate
->
parentParseState
!=
NULL
)
toppstate
=
toppstate
->
parentParseState
;
/* Check parameter number is in range */
if
(
paramno
<=
0
)
/* probably can't happen? */
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERRO
R
),
errmsg
(
"subquery must return a column"
)));
while
((
tlist_item
=
lnext
(
tlist_item
))
!=
NULL
)
(
errcode
(
ERRCODE_UNDEFINED_PARAMETE
R
),
errmsg
(
"there is no parameter $%d"
,
paramno
)));
if
(
paramno
>
toppstate
->
p_numparams
)
{
if
(
!
((
TargetEntry
*
)
lfirst
(
tlist_item
))
->
resdom
->
resjunk
)
if
(
!
toppstate
->
p_variableparams
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery must return only one column"
)));
(
errcode
(
ERRCODE_UNDEFINED_PARAMETER
),
errmsg
(
"there is no parameter $%d"
,
paramno
)));
/* Okay to enlarge param array */
if
(
toppstate
->
p_paramtypes
)
toppstate
->
p_paramtypes
=
(
Oid
*
)
repalloc
(
toppstate
->
p_paramtypes
,
paramno
*
sizeof
(
Oid
));
else
toppstate
->
p_paramtypes
=
(
Oid
*
)
palloc
(
paramno
*
sizeof
(
Oid
));
/* Zero out the previously-unreferenced slots */
MemSet
(
toppstate
->
p_paramtypes
+
toppstate
->
p_numparams
,
0
,
(
paramno
-
toppstate
->
p_numparams
)
*
sizeof
(
Oid
));
toppstate
->
p_numparams
=
paramno
;
}
if
(
toppstate
->
p_variableparams
)
{
/* If not seen before, initialize to UNKNOWN type */
if
(
toppstate
->
p_paramtypes
[
paramno
-
1
]
==
InvalidOid
)
toppstate
->
p_paramtypes
[
paramno
-
1
]
=
UNKNOWNOID
;
}
param
=
makeNode
(
Param
);
param
->
paramkind
=
PARAM_NUM
;
param
->
paramid
=
(
AttrNumber
)
paramno
;
param
->
paramtype
=
toppstate
->
p_paramtypes
[
paramno
-
1
];
return
(
Node
*
)
param
;
}
static
Node
*
transformAExprOp
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
a
->
lexpr
;
Node
*
rexpr
=
a
->
rexpr
;
Node
*
result
;
/*
* EXPR and ARRAY need no lefthand or combining
* operator. These fields should be NIL already, but
* make sure
.
* Special-case "foo = NULL" and "NULL = foo" for compatibility
* with standards-broken products (like Microsoft's). Turn these
* into IS NULL exprs
.
*/
sublink
->
lefthand
=
NIL
;
sublink
->
operName
=
NIL
;
sublink
->
operOids
=
NIL
;
sublink
->
useOr
=
FALSE
;
}
else
if
(
Transform_null_equals
&&
list_length
(
a
->
name
)
==
1
&&
strcmp
(
strVal
(
linitial
(
a
->
name
)),
"="
)
==
0
&&
(
exprIsNullConstant
(
lexpr
)
||
exprIsNullConstant
(
rexpr
)))
{
/* ALL, ANY, or MULTIEXPR: generate operator list */
List
*
left_list
=
sublink
->
lefthand
;
List
*
right_list
=
qtree
->
targetList
;
int
row_length
=
list_length
(
left_list
);
bool
needNot
=
false
;
List
*
op
=
sublink
->
operName
;
char
*
opname
=
strVal
(
llast
(
op
));
ListCell
*
l
;
ListCell
*
ll_item
;
NullTest
*
n
=
makeNode
(
NullTest
);
/* transform lefthand expressions */
foreach
(
l
,
left_list
)
lfirst
(
l
)
=
transformExpr
(
pstate
,
lfirst
(
l
));
n
->
nulltesttype
=
IS_NULL
;
if
(
exprIsNullConstant
(
lexpr
))
n
->
arg
=
(
Expr
*
)
rexpr
;
else
n
->
arg
=
(
Expr
*
)
lexpr
;
result
=
transformExpr
(
pstate
,
(
Node
*
)
n
);
}
else
if
(
lexpr
&&
IsA
(
lexpr
,
RowExpr
)
&&
rexpr
&&
IsA
(
rexpr
,
SubLink
)
&&
((
SubLink
*
)
rexpr
)
->
subLinkType
==
EXPR_SUBLINK
)
{
/*
* If the expression is "<> ALL" (with unqualified
* opname) then convert it to "NOT IN". This is a
* hack to improve efficiency of expressions output by
* pre-7.4 Postgres
.
* Convert "row op subselect" into a MULTIEXPR sublink.
* Formerly the grammar did this, but now that a row construct
* is allowed anywhere in expressions, it's easier to do it
* here
.
*/
if
(
sublink
->
subLinkType
==
ALL_SUBLINK
&&
list_length
(
op
)
==
1
&&
strcmp
(
opname
,
"<>"
)
==
0
)
SubLink
*
s
=
(
SubLink
*
)
rexpr
;
s
->
subLinkType
=
MULTIEXPR_SUBLINK
;
s
->
lefthand
=
((
RowExpr
*
)
lexpr
)
->
args
;
s
->
operName
=
a
->
name
;
result
=
transformExpr
(
pstate
,
(
Node
*
)
s
);
}
else
if
(
lexpr
&&
IsA
(
lexpr
,
RowExpr
)
&&
rexpr
&&
IsA
(
rexpr
,
RowExpr
))
{
sublink
->
subLinkType
=
ANY_SUBLINK
;
opname
=
pstrdup
(
"="
);
op
=
list_make1
(
makeString
(
opname
));
sublink
->
operName
=
op
;
needNot
=
true
;
/* "row op row" */
result
=
make_row_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
/* Set useOr if op is "<>" (possibly qualified) */
if
(
strcmp
(
opname
,
"<>"
)
==
0
)
sublink
->
useOr
=
TRUE
;
else
sublink
->
useOr
=
FALSE
;
{
/* Ordinary scalar operator */
lexpr
=
transformExpr
(
pstate
,
lexpr
);
rexpr
=
transformExpr
(
pstate
,
rexpr
);
/* Combining operators other than =/<> is dubious... */
if
(
row_length
!=
1
&&
strcmp
(
opname
,
"="
)
!=
0
&&
strcmp
(
opname
,
"<>"
)
!=
0
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_FEATURE_NOT_SUPPORTED
),
errmsg
(
"row comparison cannot use operator %s"
,
opname
)));
result
=
(
Node
*
)
make_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
/*
* To build the list of combining operator OIDs, we
* must scan subquery's targetlist to find values that
* will be matched against lefthand values. We need
* to ignore resjunk targets, so doing the outer
* iteration over right_list is easier than doing it
* over left_list.
*/
sublink
->
operOids
=
NIL
;
return
result
;
}
ll_item
=
list_head
(
left_list
);
foreach
(
l
,
right_list
)
static
Node
*
transformAExprAnd
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
lexpr
=
coerce_to_boolean
(
pstate
,
lexpr
,
"AND"
);
rexpr
=
coerce_to_boolean
(
pstate
,
rexpr
,
"AND"
);
return
(
Node
*
)
makeBoolExpr
(
AND_EXPR
,
list_make2
(
lexpr
,
rexpr
));
}
static
Node
*
transformAExprOr
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
lexpr
=
coerce_to_boolean
(
pstate
,
lexpr
,
"OR"
);
rexpr
=
coerce_to_boolean
(
pstate
,
rexpr
,
"OR"
);
return
(
Node
*
)
makeBoolExpr
(
OR_EXPR
,
list_make2
(
lexpr
,
rexpr
));
}
static
Node
*
transformAExprNot
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
rexpr
=
coerce_to_boolean
(
pstate
,
rexpr
,
"NOT"
);
return
(
Node
*
)
makeBoolExpr
(
NOT_EXPR
,
list_make1
(
rexpr
));
}
static
Node
*
transformAExprOpAny
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
return
(
Node
*
)
make_scalar_array_op
(
pstate
,
a
->
name
,
true
,
lexpr
,
rexpr
);
}
static
Node
*
transformAExprOpAll
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
return
(
Node
*
)
make_scalar_array_op
(
pstate
,
a
->
name
,
false
,
lexpr
,
rexpr
);
}
static
Node
*
transformAExprDistinct
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
a
->
lexpr
;
Node
*
rexpr
=
a
->
rexpr
;
if
(
lexpr
&&
IsA
(
lexpr
,
RowExpr
)
&&
rexpr
&&
IsA
(
rexpr
,
RowExpr
))
{
TargetEntry
*
tent
=
(
TargetEntry
*
)
lfirst
(
l
);
Node
*
lexpr
;
Operator
optup
;
Form_pg_operator
opform
;
/* "row op row" */
return
make_row_distinct_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
else
{
/* Ordinary scalar operator */
lexpr
=
transformExpr
(
pstate
,
lexpr
);
rexpr
=
transformExpr
(
pstate
,
rexpr
);
if
(
tent
->
resdom
->
resjunk
)
continue
;
return
(
Node
*
)
make_distinct_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
}
}
if
(
ll_item
==
NULL
)
static
Node
*
transformAExprNullIf
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
Node
*
rexpr
=
transformExpr
(
pstate
,
a
->
rexpr
);
Node
*
result
;
result
=
(
Node
*
)
make_op
(
pstate
,
a
->
name
,
lexpr
,
rexpr
);
if
(((
OpExpr
*
)
result
)
->
opresulttype
!=
BOOLOID
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery has too many columns"
)));
lexpr
=
lfirst
(
ll_item
);
ll_item
=
lnext
(
ll_item
);
(
errcode
(
ERRCODE_DATATYPE_MISMATCH
),
errmsg
(
"NULLIF requires = operator to yield boolean"
)));
/*
* It's OK to use oper() not compatible_oper()
* here, because make_subplan() will insert type
* coercion calls if needed.
* We rely on NullIfExpr and OpExpr being the same struct
*/
optup
=
oper
(
op
,
exprType
(
lexpr
),
exprType
((
Node
*
)
tent
->
expr
),
false
);
opform
=
(
Form_pg_operator
)
GETSTRUCT
(
optup
);
NodeSetTag
(
result
,
T_NullIfExpr
);
if
(
opform
->
oprresult
!=
BOOLOID
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_DATATYPE_MISMATCH
),
errmsg
(
"operator %s must return type boolean, not type %s"
,
opname
,
format_type_be
(
opform
->
oprresult
)),
errhint
(
"The operator of a quantified predicate subquery must return type boolean."
)));
return
result
;
}
static
Node
*
transformAExprOf
(
ParseState
*
pstate
,
A_Expr
*
a
)
{
/*
* Checking an expression for match to type. Will result in a
* boolean constant node.
*/
ListCell
*
telem
;
A_Const
*
n
;
Oid
ltype
,
rtype
;
bool
matched
=
false
;
Node
*
lexpr
=
transformExpr
(
pstate
,
a
->
lexpr
);
ltype
=
exprType
(
lexpr
);
foreach
(
telem
,
(
List
*
)
a
->
rexpr
)
{
rtype
=
LookupTypeName
(
lfirst
(
telem
));
matched
=
(
rtype
==
ltype
);
if
(
matched
)
break
;
}
if
(
get_func_retset
(
opform
->
oprcode
))
ereport
(
ERROR
,
(
errcode
(
ERRCODE_DATATYPE_MISMATCH
),
errmsg
(
"operator %s must not return a set"
,
opname
),
errhint
(
"The operator of a quantified predicate subquery must return type boolean."
))
);
/*
* Expect two forms: equals or not equals. Flip the sense of the
* result for not equals.
*/
if
(
strcmp
(
strVal
(
linitial
(
a
->
name
)),
"!="
)
==
0
)
matched
=
(
!
matched
);
sublink
->
operOids
=
lappend_oid
(
sublink
->
operOids
,
oprid
(
optup
));
n
=
makeNode
(
A_Const
);
n
->
val
.
type
=
T_String
;
n
->
val
.
val
.
str
=
(
matched
?
"t"
:
"f"
);
n
->
typename
=
SystemTypeName
(
"bool"
);
ReleaseSysCache
(
optup
);
}
if
(
ll_item
!=
NULL
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery has too few columns"
)));
return
transformExpr
(
pstate
,
(
Node
*
)
n
);
}
if
(
needNot
)
static
Node
*
transformFuncCall
(
ParseState
*
pstate
,
FuncCall
*
fn
)
{
List
*
targs
;
ListCell
*
args
;
/*
* Transform the list of arguments. We use a shallow list copy
* and then transform-in-place to avoid O(N^2) behavior from
* repeated lappend's.
*
* XXX: repeated lappend() would no longer result in O(n^2)
* behavior; worth reconsidering this design?
*/
targs
=
list_copy
(
fn
->
args
);
foreach
(
args
,
targs
)
{
expr
=
coerce_to_boolean
(
pstate
,
expr
,
"NOT"
);
expr
=
(
Node
*
)
makeBoolExpr
(
NOT_EXPR
,
list_make1
(
expr
));
}
}
result
=
(
Node
*
)
expr
;
break
;
lfirst
(
args
)
=
transformExpr
(
pstate
,
(
Node
*
)
lfirst
(
args
));
}
case
T_CaseExpr
:
{
CaseExpr
*
c
=
(
CaseExpr
*
)
expr
;
return
ParseFuncOrColumn
(
pstate
,
fn
->
funcname
,
targs
,
fn
->
agg_star
,
fn
->
agg_distinct
,
false
);
}
static
Node
*
transformCaseExpr
(
ParseState
*
pstate
,
CaseExpr
*
c
)
{
CaseExpr
*
newc
;
Node
*
arg
;
CaseTestExpr
*
placeholder
;
...
...
@@ -638,10 +810,8 @@ transformExpr(ParseState *pstate, Node *expr)
/* If we already transformed this node, do nothing */
if
(
OidIsValid
(
c
->
casetype
))
{
result
=
expr
;
break
;
}
return
(
Node
*
)
c
;
newc
=
makeNode
(
CaseExpr
);
/* transform the test expression, if any */
...
...
@@ -651,16 +821,15 @@ transformExpr(ParseState *pstate, Node *expr)
if
(
arg
)
{
/*
* If test expression is an untyped literal, force it to
* text. We have to do something now because we won't be
* able to do this coercion on the placeholder. This is
* not as flexible as what was done in 7.4 and before,
* but it's good enough to handle the sort of silly
* coding commonly seen.
* If test expression is an untyped literal, force it to text.
* We have to do something now because we won't be able to do
* this coercion on the placeholder. This is not as flexible
* as what was done in 7.4 and before, but it's good enough to
* handle the sort of silly coding commonly seen.
*/
if
(
exprType
(
arg
)
==
UNKNOWNOID
)
arg
=
coerce_to_common_type
(
pstate
,
arg
,
TEXTOID
,
"CASE"
);
arg
=
coerce_to_common_type
(
pstate
,
arg
,
TEXTOID
,
"CASE"
);
placeholder
=
makeNode
(
CaseTestExpr
);
placeholder
->
typeId
=
exprType
(
arg
);
placeholder
->
typeMod
=
exprTypmod
(
arg
);
...
...
@@ -716,491 +885,388 @@ transformExpr(ParseState *pstate, Node *expr)
newc
->
defresult
=
(
Expr
*
)
transformExpr
(
pstate
,
defresult
);
/*
* Note: default result is considered the most significant
* type in determining preferred type. This is how the
* code worked before,
but it seems a little bogus to me
* Note: default result is considered the most significant type in
* determining preferred type. This is how the code worked before,
*
but it seems a little bogus to me
* --- tgl
*/
typeids
=
lcons_oid
(
exprType
((
Node
*
)
newc
->
defresult
),
typeids
);
ptype
=
select_common_type
(
typeids
,
"CASE"
);
Assert
(
OidIsValid
(
ptype
));
newc
->
casetype
=
ptype
;
/* Convert default result clause, if necessary */
newc
->
defresult
=
(
Expr
*
)
coerce_to_common_type
(
pstate
,
(
Node
*
)
newc
->
defresult
,
ptype
,
"CASE/ELSE"
);
/* Convert when-clause results, if necessary */
foreach
(
l
,
newc
->
args
)
{
CaseWhen
*
w
=
(
CaseWhen
*
)
lfirst
(
l
);
w
->
result
=
(
Expr
*
)
coerce_to_common_type
(
pstate
,
(
Node
*
)
w
->
result
,
ptype
,
"CASE/WHEN"
);
}
result
=
(
Node
*
)
newc
;
break
;
}
case
T_ArrayExpr
:
{
ArrayExpr
*
a
=
(
ArrayExpr
*
)
expr
;
ArrayExpr
*
newa
=
makeNode
(
ArrayExpr
);
List
*
newelems
=
NIL
;
List
*
newcoercedelems
=
NIL
;
List
*
typeids
=
NIL
;
ListCell
*
element
;
Oid
array_type
;
Oid
element_type
;
/* Transform the element expressions */
foreach
(
element
,
a
->
elements
)
{
Node
*
e
=
(
Node
*
)
lfirst
(
element
);
Node
*
newe
;
newe
=
transformExpr
(
pstate
,
e
);
newelems
=
lappend
(
newelems
,
newe
);
typeids
=
lappend_oid
(
typeids
,
exprType
(
newe
));
}
/* Select a common type for the elements */
element_type
=
select_common_type
(
typeids
,
"ARRAY"
);
/* Coerce arguments to common type if necessary */
foreach
(
element
,
newelems
)
{
Node
*
e
=
(
Node
*
)
lfirst
(
element
);
Node
*
newe
;
newe
=
coerce_to_common_type
(
pstate
,
e
,
element_type
,
"ARRAY"
);
newcoercedelems
=
lappend
(
newcoercedelems
,
newe
);
}
/* Do we have an array type to use? */
array_type
=
get_array_type
(
element_type
);
if
(
array_type
!=
InvalidOid
)
{
/* Elements are presumably of scalar type */
newa
->
multidims
=
false
;
}
else
{
/* Must be nested array expressions */
newa
->
multidims
=
true
;
array_type
=
element_type
;
element_type
=
get_element_type
(
array_type
);
if
(
!
OidIsValid
(
element_type
))
ereport
(
ERROR
,
(
errcode
(
ERRCODE_UNDEFINED_OBJECT
),
errmsg
(
"could not find array type for data type %s"
,
format_type_be
(
array_type
))));
}
newa
->
array_typeid
=
array_type
;
newa
->
element_typeid
=
element_type
;
newa
->
elements
=
newcoercedelems
;
result
=
(
Node
*
)
newa
;
break
;
}
case
T_RowExpr
:
{
RowExpr
*
r
=
(
RowExpr
*
)
expr
;
RowExpr
*
newr
=
makeNode
(
RowExpr
);
List
*
newargs
=
NIL
;
ListCell
*
arg
;
/* Transform the field expressions */
foreach
(
arg
,
r
->
args
)
{
Node
*
e
=
(
Node
*
)
lfirst
(
arg
);
Node
*
newe
;
newe
=
transformExpr
(
pstate
,
e
);
newargs
=
lappend
(
newargs
,
newe
);
}
newr
->
args
=
newargs
;
/* Barring later casting, we consider the type RECORD */
newr
->
row_typeid
=
RECORDOID
;
newr
->
row_format
=
COERCE_IMPLICIT_CAST
;
result
=
(
Node
*
)
newr
;
break
;
}
case
T_CoalesceExpr
:
{
CoalesceExpr
*
c
=
(
CoalesceExpr
*
)
expr
;
CoalesceExpr
*
newc
=
makeNode
(
CoalesceExpr
);
List
*
newargs
=
NIL
;
List
*
newcoercedargs
=
NIL
;
List
*
typeids
=
NIL
;
ListCell
*
args
;
foreach
(
args
,
c
->
args
)
{
Node
*
e
=
(
Node
*
)
lfirst
(
args
);
Node
*
newe
;
newe
=
transformExpr
(
pstate
,
e
);
newargs
=
lappend
(
newargs
,
newe
);
typeids
=
lappend_oid
(
typeids
,
exprType
(
newe
));
}
newc
->
coalescetype
=
select_common_type
(
typeids
,
"COALESCE"
);
/* Convert arguments if necessary */
foreach
(
args
,
newargs
)
{
Node
*
e
=
(
Node
*
)
lfirst
(
args
);
Node
*
newe
;
newe
=
coerce_to_common_type
(
pstate
,
e
,
newc
->
coalescetype
,
"COALESCE"
);
newcoercedargs
=
lappend
(
newcoercedargs
,
newe
);
}
newc
->
args
=
newcoercedargs
;
result
=
(
Node
*
)
newc
;
break
;
}
case
T_NullTest
:
{
NullTest
*
n
=
(
NullTest
*
)
expr
;
n
->
arg
=
(
Expr
*
)
transformExpr
(
pstate
,
(
Node
*
)
n
->
arg
);
/* the argument can be any type, so don't coerce it */
result
=
expr
;
break
;
}
case
T_BooleanTest
:
{
BooleanTest
*
b
=
(
BooleanTest
*
)
expr
;
const
char
*
clausename
;
switch
(
b
->
booltesttype
)
{
case
IS_TRUE
:
clausename
=
"IS TRUE"
;
break
;
case
IS_NOT_TRUE
:
clausename
=
"IS NOT TRUE"
;
break
;
case
IS_FALSE
:
clausename
=
"IS FALSE"
;
break
;
case
IS_NOT_FALSE
:
clausename
=
"IS NOT FALSE"
;
break
;
case
IS_UNKNOWN
:
clausename
=
"IS UNKNOWN"
;
break
;
case
IS_NOT_UNKNOWN
:
clausename
=
"IS NOT UNKNOWN"
;
break
;
default:
elog
(
ERROR
,
"unrecognized booltesttype: %d"
,
(
int
)
b
->
booltesttype
);
clausename
=
NULL
;
/* keep compiler quiet */
}
b
->
arg
=
(
Expr
*
)
transformExpr
(
pstate
,
(
Node
*
)
b
->
arg
);
b
->
arg
=
(
Expr
*
)
coerce_to_boolean
(
pstate
,
(
Node
*
)
b
->
arg
,
clausename
);
typeids
=
lcons_oid
(
exprType
((
Node
*
)
newc
->
defresult
),
typeids
);
result
=
expr
;
break
;
}
ptype
=
select_common_type
(
typeids
,
"CASE"
)
;
Assert
(
OidIsValid
(
ptype
))
;
newc
->
casetype
=
ptype
;
/*********************************************
* Quietly accept node types that may be presented when we are
* called on an already-transformed tree.
*
* Do any other node types need to be accepted? For now we are
* taking a conservative approach, and only accepting node
* types that are demonstrably necessary to accept.
*********************************************/
case
T_Var
:
case
T_Const
:
case
T_Param
:
case
T_Aggref
:
case
T_ArrayRef
:
case
T_FuncExpr
:
case
T_OpExpr
:
case
T_DistinctExpr
:
case
T_ScalarArrayOpExpr
:
case
T_NullIfExpr
:
case
T_BoolExpr
:
case
T_FieldSelect
:
case
T_FieldStore
:
case
T_RelabelType
:
case
T_ConvertRowtypeExpr
:
case
T_CaseTestExpr
:
case
T_CoerceToDomain
:
case
T_CoerceToDomainValue
:
case
T_SetToDefault
:
/* Convert default result clause, if necessary */
newc
->
defresult
=
(
Expr
*
)
coerce_to_common_type
(
pstate
,
(
Node
*
)
newc
->
defresult
,
ptype
,
"CASE/ELSE"
);
/* Convert when-clause results, if necessary */
foreach
(
l
,
newc
->
args
)
{
result
=
(
Node
*
)
expr
;
break
;
}
CaseWhen
*
w
=
(
CaseWhen
*
)
lfirst
(
l
);
default:
/* should not reach here */
elog
(
ERROR
,
"unrecognized node type: %d"
,
(
int
)
nodeTag
(
expr
));
break
;
w
->
result
=
(
Expr
*
)
coerce_to_common_type
(
pstate
,
(
Node
*
)
w
->
result
,
ptype
,
"CASE/WHEN"
);
}
return
result
;
return
(
Node
*
)
newc
;
}
static
Node
*
transform
Indirection
(
ParseState
*
pstate
,
Node
*
basenode
,
List
*
indirection
)
transform
SubLink
(
ParseState
*
pstate
,
SubLink
*
sublink
)
{
Node
*
result
=
basenode
;
List
*
subscripts
=
NIL
;
ListCell
*
i
;
List
*
qtrees
;
Query
*
qtree
;
Node
*
result
=
(
Node
*
)
sublink
;
/* If we already transformed this node, do nothing */
if
(
IsA
(
sublink
->
subselect
,
Query
))
return
result
;
pstate
->
p_hasSubLinks
=
true
;
qtrees
=
parse_sub_analyze
(
sublink
->
subselect
,
pstate
);
if
(
list_length
(
qtrees
)
!=
1
)
elog
(
ERROR
,
"bad query in sub-select"
);
qtree
=
(
Query
*
)
linitial
(
qtrees
);
if
(
qtree
->
commandType
!=
CMD_SELECT
||
qtree
->
resultRelation
!=
0
)
elog
(
ERROR
,
"bad query in sub-select"
);
sublink
->
subselect
=
(
Node
*
)
qtree
;
if
(
sublink
->
subLinkType
==
EXISTS_SUBLINK
)
{
/*
* We have to split any field-selection operations apart from
* subscripting. Adjacent A_Indices nodes have to be treated as a
* single multidimensional subscript operation.
* EXISTS needs no lefthand or combining operator. These
* fields should be NIL already, but make sure.
*/
foreach
(
i
,
indirection
)
sublink
->
lefthand
=
NIL
;
sublink
->
operName
=
NIL
;
sublink
->
operOids
=
NIL
;
sublink
->
useOr
=
FALSE
;
}
else
if
(
sublink
->
subLinkType
==
EXPR_SUBLINK
||
sublink
->
subLinkType
==
ARRAY_SUBLINK
)
{
Node
*
n
=
lfirst
(
i
);
ListCell
*
tlist_item
=
list_head
(
qtree
->
targetList
);
if
(
IsA
(
n
,
A_Indices
))
subscripts
=
lappend
(
subscripts
,
n
);
/*
* Make sure the subselect delivers a single column (ignoring
* resjunk targets).
*/
if
(
tlist_item
==
NULL
||
((
TargetEntry
*
)
lfirst
(
tlist_item
))
->
resdom
->
resjunk
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery must return a column"
)));
while
((
tlist_item
=
lnext
(
tlist_item
))
!=
NULL
)
{
if
(
!
((
TargetEntry
*
)
lfirst
(
tlist_item
))
->
resdom
->
resjunk
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery must return only one column"
)));
}
/*
* EXPR and ARRAY need no lefthand or combining
* operator. These fields should be NIL already, but make
* sure.
*/
sublink
->
lefthand
=
NIL
;
sublink
->
operName
=
NIL
;
sublink
->
operOids
=
NIL
;
sublink
->
useOr
=
FALSE
;
}
else
{
Assert
(
IsA
(
n
,
String
));
/* ALL, ANY, or MULTIEXPR: generate operator list */
List
*
left_list
=
sublink
->
lefthand
;
List
*
right_list
=
qtree
->
targetList
;
int
row_length
=
list_length
(
left_list
);
bool
needNot
=
false
;
List
*
op
=
sublink
->
operName
;
char
*
opname
=
strVal
(
llast
(
op
));
ListCell
*
l
;
ListCell
*
ll_item
;
/* process subscripts before this field selection */
if
(
subscripts
)
result
=
(
Node
*
)
transformArraySubscripts
(
pstate
,
result
,
exprType
(
result
),
InvalidOid
,
-
1
,
subscripts
,
NULL
);
subscripts
=
NIL
;
/* transform lefthand expressions */
foreach
(
l
,
left_list
)
lfirst
(
l
)
=
transformExpr
(
pstate
,
lfirst
(
l
));
result
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
n
),
list_make1
(
result
),
false
,
false
,
true
);
}
/*
* If the expression is "<> ALL" (with unqualified opname)
* then convert it to "NOT IN". This is a hack to improve
* efficiency of expressions output by pre-7.4 Postgres.
*/
if
(
sublink
->
subLinkType
==
ALL_SUBLINK
&&
list_length
(
op
)
==
1
&&
strcmp
(
opname
,
"<>"
)
==
0
)
{
sublink
->
subLinkType
=
ANY_SUBLINK
;
opname
=
pstrdup
(
"="
);
op
=
list_make1
(
makeString
(
opname
));
sublink
->
operName
=
op
;
needNot
=
true
;
}
/* process trailing subscripts, if any */
if
(
subscripts
)
result
=
(
Node
*
)
transformArraySubscripts
(
pstate
,
result
,
exprType
(
result
),
InvalidOid
,
-
1
,
subscripts
,
NULL
);
return
result
;
}
/* Set useOr if op is "<>" (possibly qualified) */
if
(
strcmp
(
opname
,
"<>"
)
==
0
)
sublink
->
useOr
=
TRUE
;
else
sublink
->
useOr
=
FALSE
;
static
Node
*
transformColumnRef
(
ParseState
*
pstate
,
ColumnRef
*
cref
)
{
int
numnames
=
list_length
(
cref
->
fields
);
Node
*
node
;
int
levels_up
;
/* Combining operators other than =/<> is dubious... */
if
(
row_length
!=
1
&&
strcmp
(
opname
,
"="
)
!=
0
&&
strcmp
(
opname
,
"<>"
)
!=
0
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_FEATURE_NOT_SUPPORTED
),
errmsg
(
"row comparison cannot use operator %s"
,
opname
)));
/*----------
* The allowed syntaxes are:
*
* A First try to resolve as unqualified column name;
* if no luck, try to resolve as unqualified table name (A.*).
* A.B A is an unqualified table name; B is either a
* column or function name (trying column name first).
* A.B.C schema A, table B, col or func name C.
* A.B.C.D catalog A, schema B, table C, col or func D.
* A.* A is an unqualified table name; means whole-row value.
* A.B.* whole-row value of table B in schema A.
* A.B.C.* whole-row value of table C in schema B in catalog A.
*
* We do not need to cope with bare "*"; that will only be accepted by
* the grammar at the top level of a SELECT list, and transformTargetList
* will take care of it before it ever gets here. Also, "A.*" etc will
* be expanded by transformTargetList if they appear at SELECT top level,
* so here we are only going to see them as function or operator inputs.
*
* Currently, if a catalog name is given then it must equal the current
* database name; we check it here and then discard it.
*----------
/*
* To build the list of combining operator OIDs, we must scan
* subquery's targetlist to find values that will be matched
* against lefthand values. We need to ignore resjunk
* targets, so doing the outer iteration over right_list is
* easier than doing it over left_list.
*/
switch
(
numnames
)
{
case
1
:
sublink
->
operOids
=
NIL
;
ll_item
=
list_head
(
left_list
);
foreach
(
l
,
right_list
)
{
char
*
name
=
strVal
(
linitial
(
cref
->
fields
));
TargetEntry
*
tent
=
(
TargetEntry
*
)
lfirst
(
l
);
Node
*
lexpr
;
Operator
optup
;
Form_pg_operator
opform
;
/* Try to identify as an unqualified column */
node
=
colNameToVar
(
pstate
,
name
,
false
);
if
(
tent
->
resdom
->
resjunk
)
continue
;
if
(
ll_item
==
NULL
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery has too many columns"
)));
lexpr
=
lfirst
(
ll_item
);
ll_item
=
lnext
(
ll_item
);
/*
* It's OK to use oper() not compatible_oper() here,
* because make_subplan() will insert type coercion calls
* if needed.
*/
optup
=
oper
(
op
,
exprType
(
lexpr
),
exprType
((
Node
*
)
tent
->
expr
),
false
);
opform
=
(
Form_pg_operator
)
GETSTRUCT
(
optup
);
if
(
opform
->
oprresult
!=
BOOLOID
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_DATATYPE_MISMATCH
),
errmsg
(
"operator %s must return type boolean, not type %s"
,
opname
,
format_type_be
(
opform
->
oprresult
)),
errhint
(
"The operator of a quantified predicate subquery must return type boolean."
)));
if
(
get_func_retset
(
opform
->
oprcode
))
ereport
(
ERROR
,
(
errcode
(
ERRCODE_DATATYPE_MISMATCH
),
errmsg
(
"operator %s must not return a set"
,
opname
),
errhint
(
"The operator of a quantified predicate subquery must return type boolean."
)));
sublink
->
operOids
=
lappend_oid
(
sublink
->
operOids
,
oprid
(
optup
));
if
(
node
==
NULL
)
{
/*
* Not known as a column of any range-table entry.
*
* Consider the possibility that it's VALUE in a domain
* check expression. (We handle VALUE as a name, not
* a keyword, to avoid breaking a lot of applications
* that have used VALUE as a column name in the past.)
*/
if
(
pstate
->
p_value_substitute
!=
NULL
&&
strcmp
(
name
,
"value"
)
==
0
)
{
node
=
(
Node
*
)
copyObject
(
pstate
->
p_value_substitute
);
break
;
ReleaseSysCache
(
optup
);
}
/*
* Try to find the name as a relation. Note that only
* relations already entered into the rangetable will
* be recognized.
*
* This is a hack for backwards compatibility with
* PostQUEL-inspired syntax. The preferred form now
* is "rel.*".
*/
if
(
refnameRangeTblEntry
(
pstate
,
NULL
,
name
,
&
levels_up
)
!=
NULL
)
node
=
transformWholeRowRef
(
pstate
,
NULL
,
name
);
else
if
(
ll_item
!=
NULL
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_UNDEFINED_COLUMN
),
errmsg
(
"column
\"
%s
\"
does not exist"
,
name
)));
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"subquery has too few columns"
)));
if
(
needNot
)
{
result
=
coerce_to_boolean
(
pstate
,
result
,
"NOT"
);
result
=
(
Node
*
)
makeBoolExpr
(
NOT_EXPR
,
list_make1
(
result
));
}
break
;
}
case
2
:
{
char
*
name1
=
strVal
(
linitial
(
cref
->
fields
));
char
*
name2
=
strVal
(
lsecond
(
cref
->
fields
));
/* Whole-row reference? */
if
(
strcmp
(
name2
,
"*"
)
==
0
)
return
result
;
}
static
Node
*
transformArrayExpr
(
ParseState
*
pstate
,
ArrayExpr
*
a
)
{
ArrayExpr
*
newa
=
makeNode
(
ArrayExpr
);
List
*
newelems
=
NIL
;
List
*
newcoercedelems
=
NIL
;
List
*
typeids
=
NIL
;
ListCell
*
element
;
Oid
array_type
;
Oid
element_type
;
/* Transform the element expressions */
foreach
(
element
,
a
->
elements
)
{
node
=
transformWholeRowRef
(
pstate
,
NULL
,
name1
);
break
;
Node
*
e
=
(
Node
*
)
lfirst
(
element
);
Node
*
newe
;
newe
=
transformExpr
(
pstate
,
e
);
newelems
=
lappend
(
newelems
,
newe
);
typeids
=
lappend_oid
(
typeids
,
exprType
(
newe
));
}
/* Try to identify as a once-qualified column */
node
=
qualifiedNameToVar
(
pstate
,
NULL
,
name1
,
name2
,
true
);
if
(
node
==
NULL
)
/* Select a common type for the elements */
element_type
=
select_common_type
(
typeids
,
"ARRAY"
);
/* Coerce arguments to common type if necessary */
foreach
(
element
,
newelems
)
{
/*
* Not known as a column of any range-table entry, so
* try it as a function call. Here, we will create an
* implicit RTE for tables not already entered.
*/
node
=
transformWholeRowRef
(
pstate
,
NULL
,
name1
);
node
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
makeString
(
name2
)),
list_make1
(
node
),
false
,
false
,
true
);
Node
*
e
=
(
Node
*
)
lfirst
(
element
);
Node
*
newe
;
newe
=
coerce_to_common_type
(
pstate
,
e
,
element_type
,
"ARRAY"
);
newcoercedelems
=
lappend
(
newcoercedelems
,
newe
);
}
break
;
/* Do we have an array type to use? */
array_type
=
get_array_type
(
element_type
);
if
(
array_type
!=
InvalidOid
)
{
/* Elements are presumably of scalar type */
newa
->
multidims
=
false
;
}
case
3
:
else
{
char
*
name1
=
strVal
(
linitial
(
cref
->
fields
));
char
*
name2
=
strVal
(
lsecond
(
cref
->
fields
));
char
*
name3
=
strVal
(
lthird
(
cref
->
fields
));
/* Must be nested array expressions */
newa
->
multidims
=
true
;
/* Whole-row reference? */
if
(
strcmp
(
name3
,
"*"
)
==
0
)
{
node
=
transformWholeRowRef
(
pstate
,
name1
,
name2
);
break
;
array_type
=
element_type
;
element_type
=
get_element_type
(
array_type
);
if
(
!
OidIsValid
(
element_type
))
ereport
(
ERROR
,
(
errcode
(
ERRCODE_UNDEFINED_OBJECT
),
errmsg
(
"could not find array type for data type %s"
,
format_type_be
(
array_type
))));
}
/* Try to identify as a twice-qualified column */
node
=
qualifiedNameToVar
(
pstate
,
name1
,
name2
,
name3
,
true
);
if
(
node
==
NULL
)
newa
->
array_typeid
=
array_type
;
newa
->
element_typeid
=
element_type
;
newa
->
elements
=
newcoercedelems
;
return
(
Node
*
)
newa
;
}
static
Node
*
transformRowExpr
(
ParseState
*
pstate
,
RowExpr
*
r
)
{
RowExpr
*
newr
=
makeNode
(
RowExpr
);
List
*
newargs
=
NIL
;
ListCell
*
arg
;
/* Transform the field expressions */
foreach
(
arg
,
r
->
args
)
{
/* Try it as a function call */
node
=
transformWholeRowRef
(
pstate
,
name1
,
name2
);
node
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
makeString
(
name3
)),
list_make1
(
node
),
false
,
false
,
true
);
}
break
;
Node
*
e
=
(
Node
*
)
lfirst
(
arg
);
Node
*
newe
;
newe
=
transformExpr
(
pstate
,
e
);
newargs
=
lappend
(
newargs
,
newe
);
}
case
4
:
{
char
*
name1
=
strVal
(
linitial
(
cref
->
fields
));
char
*
name2
=
strVal
(
lsecond
(
cref
->
fields
));
char
*
name3
=
strVal
(
lthird
(
cref
->
fields
));
char
*
name4
=
strVal
(
lfourth
(
cref
->
fields
));
newr
->
args
=
newargs
;
/*
* We check the catalog name and then ignore it.
*/
if
(
strcmp
(
name1
,
get_database_name
(
MyDatabaseId
))
!=
0
)
ereport
(
ERROR
,
(
errcode
(
ERRCODE_FEATURE_NOT_SUPPORTED
),
errmsg
(
"cross-database references are not implemented: %s"
,
NameListToString
(
cref
->
fields
))));
/* Barring later casting, we consider the type RECORD */
newr
->
row_typeid
=
RECORDOID
;
newr
->
row_format
=
COERCE_IMPLICIT_CAST
;
/* Whole-row reference? */
if
(
strcmp
(
name4
,
"*"
)
==
0
)
return
(
Node
*
)
newr
;
}
static
Node
*
transformCoalesceExpr
(
ParseState
*
pstate
,
CoalesceExpr
*
c
)
{
CoalesceExpr
*
newc
=
makeNode
(
CoalesceExpr
);
List
*
newargs
=
NIL
;
List
*
newcoercedargs
=
NIL
;
List
*
typeids
=
NIL
;
ListCell
*
args
;
foreach
(
args
,
c
->
args
)
{
node
=
transformWholeRowRef
(
pstate
,
name2
,
name3
);
break
;
Node
*
e
=
(
Node
*
)
lfirst
(
args
);
Node
*
newe
;
newe
=
transformExpr
(
pstate
,
e
);
newargs
=
lappend
(
newargs
,
newe
);
typeids
=
lappend_oid
(
typeids
,
exprType
(
newe
));
}
/* Try to identify as a twice-qualified column */
node
=
qualifiedNameToVar
(
pstate
,
name2
,
name3
,
name4
,
true
);
if
(
node
==
NULL
)
newc
->
coalescetype
=
select_common_type
(
typeids
,
"COALESCE"
);
/* Convert arguments if necessary */
foreach
(
args
,
newargs
)
{
/* Try it as a function call */
node
=
transformWholeRowRef
(
pstate
,
name2
,
name3
);
node
=
ParseFuncOrColumn
(
pstate
,
list_make1
(
makeString
(
name4
)),
list_make1
(
node
),
false
,
false
,
true
);
Node
*
e
=
(
Node
*
)
lfirst
(
args
);
Node
*
newe
;
newe
=
coerce_to_common_type
(
pstate
,
e
,
newc
->
coalescetype
,
"COALESCE"
);
newcoercedargs
=
lappend
(
newcoercedargs
,
newe
);
}
newc
->
args
=
newcoercedargs
;
return
(
Node
*
)
newc
;
}
static
Node
*
transformBooleanTest
(
ParseState
*
pstate
,
BooleanTest
*
b
)
{
const
char
*
clausename
;
switch
(
b
->
booltesttype
)
{
case
IS_TRUE
:
clausename
=
"IS TRUE"
;
break
;
}
default:
ereport
(
ERROR
,
(
errcode
(
ERRCODE_SYNTAX_ERROR
),
errmsg
(
"improper qualified name (too many dotted names): %s"
,
NameListToString
(
cref
->
fields
))));
node
=
NULL
;
/* keep compiler quiet */
case
IS_NOT_TRUE
:
clausename
=
"IS NOT TRUE"
;
break
;
case
IS_FALSE
:
clausename
=
"IS FALSE"
;
break
;
case
IS_NOT_FALSE
:
clausename
=
"IS NOT FALSE"
;
break
;
case
IS_UNKNOWN
:
clausename
=
"IS UNKNOWN"
;
break
;
case
IS_NOT_UNKNOWN
:
clausename
=
"IS NOT UNKNOWN"
;
break
;
default:
elog
(
ERROR
,
"unrecognized booltesttype: %d"
,
(
int
)
b
->
booltesttype
);
clausename
=
NULL
;
/* keep compiler quiet */
}
return
node
;
b
->
arg
=
(
Expr
*
)
transformExpr
(
pstate
,
(
Node
*
)
b
->
arg
);
b
->
arg
=
(
Expr
*
)
coerce_to_boolean
(
pstate
,
(
Node
*
)
b
->
arg
,
clausename
);
return
(
Node
*
)
b
;
}
/*
...
...
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