Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Greenplum
Gpdb
提交
681ed4e2
G
Gpdb
项目概览
Greenplum
/
Gpdb
通知
7
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
Gpdb
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
681ed4e2
编写于
9月 01, 2002
作者:
T
Tom Lane
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Code cleanups: make non-implicit WITHOUT FUNCTION casts work, avoid
redundant pg_cast searches, fix obsolete comments.
上级
a03f9adb
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
266 addition
and
299 deletion
+266
-299
src/backend/parser/parse_coerce.c
src/backend/parser/parse_coerce.c
+266
-299
未找到文件。
src/backend/parser/parse_coerce.c
浏览文件 @
681ed4e2
...
...
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.8
1 2002/08/31 22:10:46
tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.8
2 2002/09/01 02:27:32
tgl Exp $
*
*-------------------------------------------------------------------------
*/
...
...
@@ -27,24 +27,27 @@
#include "utils/syscache.h"
Oid
DemoteType
(
Oid
inType
);
Oid
PromoteTypeToNext
(
Oid
inType
);
static
Oid
PreferredType
(
CATEGORY
category
,
Oid
type
);
static
Node
*
build_func_call
(
Oid
funcid
,
Oid
rettype
,
List
*
args
);
static
Oid
find_coercion_function
(
Oid
targetTypeId
,
Oid
sourceTypeId
,
bool
isExplicit
);
static
bool
find_coercion_pathway
(
Oid
targetTypeId
,
Oid
sourceTypeId
,
bool
isExplicit
,
Oid
*
funcid
);
static
Oid
find_typmod_coercion_function
(
Oid
typeId
);
static
Node
*
build_func_call
(
Oid
funcid
,
Oid
rettype
,
List
*
args
);
/* coerce_type()
* Convert a function argument to a different type.
/*
* coerce_type()
* Convert a function argument to a different type.
*
* The caller should already have determined that the coercion is possible;
* see can_coerce_type.
*/
Node
*
coerce_type
(
ParseState
*
pstate
,
Node
*
node
,
Oid
inputTypeId
,
Oid
targetTypeId
,
int32
atttypmod
,
bool
isExplicit
)
{
Node
*
result
;
Oid
funcId
;
if
(
targetTypeId
==
inputTypeId
||
node
==
NULL
)
...
...
@@ -118,25 +121,71 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
/* assume can_coerce_type verified that implicit coercion is okay */
result
=
node
;
}
else
if
(
IsBinaryCompatible
(
inputTypeId
,
targetTypeId
))
else
if
(
find_coercion_pathway
(
targetTypeId
,
inputTypeId
,
isExplicit
,
&
funcId
))
{
/*
* We don't really need to do a conversion, but we do need to
* attach a RelabelType node so that the expression will be seen
* to have the intended type when inspected by higher-level code.
*
* Also, domains may have value restrictions beyond the base type
* that must be accounted for.
*/
result
=
coerce_type_constraints
(
pstate
,
node
,
targetTypeId
,
true
);
/*
* XXX could we label result with exprTypmod(node) instead of
* default -1 typmod, to save a possible length-coercion later?
* Would work if both types have same interpretation of typmod,
* which is likely but not certain (wrong if target is a domain,
* in any case).
*/
result
=
(
Node
*
)
makeRelabelType
(
result
,
targetTypeId
,
-
1
);
if
(
OidIsValid
(
funcId
))
{
/*
* Generate an expression tree representing run-time application
* of the conversion function. If we are dealing with a domain
* target type, the conversion function will yield the base type.
*/
Oid
baseTypeId
=
getBaseType
(
targetTypeId
);
result
=
build_func_call
(
funcId
,
baseTypeId
,
makeList1
(
node
));
/*
* If domain, test against domain constraints and relabel with
* domain type ID
*/
if
(
targetTypeId
!=
baseTypeId
)
{
result
=
coerce_type_constraints
(
pstate
,
result
,
targetTypeId
,
true
);
result
=
(
Node
*
)
makeRelabelType
(
result
,
targetTypeId
,
-
1
);
}
/*
* If the input is a constant, apply the type conversion function
* now instead of delaying to runtime. (We could, of course, just
* leave this to be done during planning/optimization; but it's a
* very frequent special case, and we save cycles in the rewriter
* if we fold the expression now.)
*
* Note that no folding will occur if the conversion function is
* not marked 'immutable'.
*
* HACK: if constant is NULL, don't fold it here. This is needed
* by make_subplan(), which calls this routine on placeholder
* Const nodes that mustn't be collapsed. (It'd be a lot cleaner
* to make a separate node type for that purpose...)
*/
if
(
IsA
(
node
,
Const
)
&&
!
((
Const
*
)
node
)
->
constisnull
)
result
=
eval_const_expressions
(
result
);
}
else
{
/*
* We don't need to do a physical conversion, but we do need to
* attach a RelabelType node so that the expression will be seen
* to have the intended type when inspected by higher-level code.
*
* Also, domains may have value restrictions beyond the base type
* that must be accounted for.
*/
result
=
coerce_type_constraints
(
pstate
,
node
,
targetTypeId
,
true
);
/*
* XXX could we label result with exprTypmod(node) instead of
* default -1 typmod, to save a possible length-coercion later?
* Would work if both types have same interpretation of typmod,
* which is likely but not certain (wrong if target is a domain,
* in any case).
*/
result
=
(
Node
*
)
makeRelabelType
(
result
,
targetTypeId
,
-
1
);
}
}
else
if
(
typeInheritsFrom
(
inputTypeId
,
targetTypeId
))
{
...
...
@@ -149,74 +198,23 @@ coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
}
else
{
/*
* Otherwise, find the appropriate type conversion function
* (caller should have determined that there is one), and generate
* an expression tree representing run-time application of the
* conversion function.
*
* For domains, we use the coercion function for the base type.
*/
Oid
baseTypeId
=
getBaseType
(
targetTypeId
);
Oid
funcId
;
funcId
=
find_coercion_function
(
baseTypeId
,
getBaseType
(
inputTypeId
),
isExplicit
);
if
(
!
OidIsValid
(
funcId
))
elog
(
ERROR
,
"coerce_type: no conversion function from '%s' to '%s'"
,
format_type_be
(
inputTypeId
),
format_type_be
(
targetTypeId
));
result
=
build_func_call
(
funcId
,
baseTypeId
,
makeList1
(
node
));
/*
* If domain, test against domain constraints and relabel with
* domain type ID
*/
if
(
targetTypeId
!=
baseTypeId
)
{
result
=
coerce_type_constraints
(
pstate
,
result
,
targetTypeId
,
true
);
result
=
(
Node
*
)
makeRelabelType
(
result
,
targetTypeId
,
-
1
);
}
/*
* If the input is a constant, apply the type conversion function
* now instead of delaying to runtime. (We could, of course, just
* leave this to be done during planning/optimization; but it's a
* very frequent special case, and we save cycles in the rewriter
* if we fold the expression now.)
*
* Note that no folding will occur if the conversion function is not
* marked 'iscachable'.
*
* HACK: if constant is NULL, don't fold it here. This is needed by
* make_subplan(), which calls this routine on placeholder Const
* nodes that mustn't be collapsed. (It'd be a lot cleaner to
* make a separate node type for that purpose...)
*/
if
(
IsA
(
node
,
Const
)
&&
!
((
Const
*
)
node
)
->
constisnull
)
result
=
eval_const_expressions
(
result
);
/* If we get here, caller blew it */
elog
(
ERROR
,
"coerce_type: no conversion function from %s to %s"
,
format_type_be
(
inputTypeId
),
format_type_be
(
targetTypeId
));
result
=
NULL
;
/* keep compiler quiet */
}
return
result
;
}
/* can_coerce_type()
* Can input_typeids be coerced to func_typeids?
*
* There are a few types which are known apriori to be convertible.
* We will check for those cases first, and then look for possible
* conversion functions.
/*
* can_coerce_type()
* Can input_typeids be coerced to func_typeids?
*
* We must be told whether this is an implicit or explicit coercion
* (explicit being a CAST construct, explicit function call, etc).
* We will accept a wider set of coercion cases for an explicit coercion.
*
* Notes:
* This uses the same mechanism as the CAST() SQL construct in gram.y.
*/
bool
can_coerce_type
(
int
nargs
,
Oid
*
input_typeids
,
Oid
*
func_typeids
,
...
...
@@ -278,35 +276,101 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *func_typeids,
}
/*
*
one of the known-good transparent conversions? then drop
*
through..
.
*
If pg_cast shows that we can coerce, accept. This test now
*
covers both binary-compatible and coercion-function cases
.
*/
if
(
IsBinaryCompatible
(
inputTypeId
,
targetTypeId
))
if
(
find_coercion_pathway
(
targetTypeId
,
inputTypeId
,
isExplicit
,
&
funcId
))
continue
;
/*
* If input is a class type that inherits from target,
no problem
* If input is a class type that inherits from target,
accept
*/
if
(
typeInheritsFrom
(
inputTypeId
,
targetTypeId
))
continue
;
/*
* Else, try for run-time conversion using functions: look for a
* single-argument function named with the target type name and
* accepting the source type.
*
* If either type is a domain, use its base type instead.
* Else, cannot coerce at this argument position
*/
funcId
=
find_coercion_function
(
getBaseType
(
targetTypeId
),
getBaseType
(
inputTypeId
),
isExplicit
);
if
(
!
OidIsValid
(
funcId
))
return
false
;
return
false
;
}
return
true
;
}
/*
* Create an expression tree to enforce the constraints (if any)
* that should be applied by the type. Currently this is only
* interesting for domain types.
*/
Node
*
coerce_type_constraints
(
ParseState
*
pstate
,
Node
*
arg
,
Oid
typeId
,
bool
applyTypmod
)
{
char
*
notNull
=
NULL
;
int32
typmod
=
-
1
;
for
(;;)
{
HeapTuple
tup
;
Form_pg_type
typTup
;
tup
=
SearchSysCache
(
TYPEOID
,
ObjectIdGetDatum
(
typeId
),
0
,
0
,
0
);
if
(
!
HeapTupleIsValid
(
tup
))
elog
(
ERROR
,
"coerce_type_constraints: failed to lookup type %u"
,
typeId
);
typTup
=
(
Form_pg_type
)
GETSTRUCT
(
tup
);
/* Test for NOT NULL Constraint */
if
(
typTup
->
typnotnull
&&
notNull
==
NULL
)
notNull
=
pstrdup
(
NameStr
(
typTup
->
typname
));
/* TODO: Add CHECK Constraints to domains */
if
(
typTup
->
typtype
!=
'd'
)
{
/* Not a domain, so done */
ReleaseSysCache
(
tup
);
break
;
}
Assert
(
typmod
<
0
);
typeId
=
typTup
->
typbasetype
;
typmod
=
typTup
->
typtypmod
;
ReleaseSysCache
(
tup
);
}
/*
* If domain applies a typmod to its base type, do length coercion.
*/
if
(
applyTypmod
&&
typmod
>=
0
)
arg
=
coerce_type_typmod
(
pstate
,
arg
,
typeId
,
typmod
);
/*
* Only need to add one NOT NULL check regardless of how many
* domains in the stack request it. The topmost domain that
* requested it is used as the constraint name.
*/
if
(
notNull
)
{
ConstraintTest
*
r
=
makeNode
(
ConstraintTest
);
r
->
arg
=
arg
;
r
->
testtype
=
CONSTR_TEST_NOTNULL
;
r
->
name
=
notNull
;
r
->
check_expr
=
NULL
;
arg
=
(
Node
*
)
r
;
}
return
arg
;
}
/* coerce_type_typmod()
* Force a value to a particular typmod, if meaningful and possible.
*
...
...
@@ -317,21 +381,9 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *func_typeids,
* The caller must have already ensured that the value is of the correct
* type, typically by applying coerce_type.
*
* If the target column type possesses a function named for the type
* and having parameter signature (columntype, int4), we assume that
* the type requires coercion to its own length and that the said
* function should be invoked to do that.
*
* "bpchar" (ie, char(N)) and "numeric" are examples of such types.
*
* This mechanism may seem pretty grotty and in need of replacement by
* something in pg_cast, but since typmod is only interesting for datatypes
* that have special handling in the grammar, there's not really much
* percentage in making it any easier to apply such coercions ...
*
* NOTE: this does not need to work on domain types, because any typmod
* coercion for a domain is considered to be part of the type coercion
* needed to produce the domain value in the first place.
* needed to produce the domain value in the first place.
So, no getBaseType.
*/
Node
*
coerce_type_typmod
(
ParseState
*
pstate
,
Node
*
node
,
...
...
@@ -600,100 +652,6 @@ TypeCategory(Oid inType)
}
/* TypeCategory() */
/* IsBinaryCompatible()
* Check if two types are binary-compatible.
*
* This notion allows us to cheat and directly exchange values without
* going through the trouble of calling a conversion function.
*
* XXX This should be moved to system catalog lookups
* to allow for better type extensibility.
*/
#define TypeIsTextGroup(t) \
((t) == TEXTOID || \
(t) == BPCHAROID || \
(t) == VARCHAROID)
/* Notice OidGroup is a subset of Int4GroupA */
#define TypeIsOidGroup(t) \
((t) == OIDOID || \
(t) == REGPROCOID || \
(t) == REGPROCEDUREOID || \
(t) == REGOPEROID || \
(t) == REGOPERATOROID || \
(t) == REGCLASSOID || \
(t) == REGTYPEOID)
/*
* INT4 is binary-compatible with many types, but we don't want to allow
* implicit coercion directly between, say, OID and AbsTime. So we subdivide
* the categories.
*/
#define TypeIsInt4GroupA(t) \
((t) == INT4OID || \
TypeIsOidGroup(t))
#define TypeIsInt4GroupB(t) \
((t) == INT4OID || \
(t) == ABSTIMEOID)
#define TypeIsInt4GroupC(t) \
((t) == INT4OID || \
(t) == RELTIMEOID)
#define TypeIsInetGroup(t) \
((t) == INETOID || \
(t) == CIDROID)
#define TypeIsBitGroup(t) \
((t) == BITOID || \
(t) == VARBITOID)
static
bool
DirectlyBinaryCompatible
(
Oid
type1
,
Oid
type2
)
{
HeapTuple
tuple
;
bool
result
;
if
(
type1
==
type2
)
return
true
;
tuple
=
SearchSysCache
(
CASTSOURCETARGET
,
type1
,
type2
,
0
,
0
);
if
(
HeapTupleIsValid
(
tuple
))
{
Form_pg_cast
caststruct
;
caststruct
=
(
Form_pg_cast
)
GETSTRUCT
(
tuple
);
result
=
caststruct
->
castfunc
==
InvalidOid
&&
caststruct
->
castimplicit
;
ReleaseSysCache
(
tuple
);
}
else
result
=
false
;
return
result
;
}
bool
IsBinaryCompatible
(
Oid
type1
,
Oid
type2
)
{
if
(
DirectlyBinaryCompatible
(
type1
,
type2
))
return
true
;
/*
* Perhaps the types are domains; if so, look at their base types
*/
if
(
OidIsValid
(
type1
))
type1
=
getBaseType
(
type1
);
if
(
OidIsValid
(
type2
))
type2
=
getBaseType
(
type2
);
if
(
DirectlyBinaryCompatible
(
type1
,
type2
))
return
true
;
return
false
;
}
/* IsPreferredType()
* Check if this type is a preferred type.
* XXX This should be moved to system catalog lookups
...
...
@@ -733,7 +691,13 @@ PreferredType(CATEGORY category, Oid type)
break
;
case
(
NUMERIC_TYPE
):
if
(
TypeIsOidGroup
(
type
))
if
(
type
==
OIDOID
||
type
==
REGPROCOID
||
type
==
REGPROCEDUREOID
||
type
==
REGOPEROID
||
type
==
REGOPERATOROID
||
type
==
REGCLASSOID
||
type
==
REGTYPEOID
)
result
=
OIDOID
;
else
if
(
type
==
NUMERICOID
)
result
=
NUMERICOID
;
...
...
@@ -768,30 +732,85 @@ PreferredType(CATEGORY category, Oid type)
return
result
;
}
/* PreferredType() */
/*
* find_coercion_function
*
Look for a coercion function between two types
.
/* IsBinaryCompatible()
*
Check if two types are binary-compatible
.
*
* A coercion function must be named after (the internal name of) its
* result type, and must accept exactly the specified input type. We
* also require it to be defined in the same namespace as its result type.
* Furthermore, unless we are doing explicit coercion the function must
* be marked as usable for implicit coercion --- this allows coercion
* functions to be provided that aren't implicitly invokable.
* This notion allows us to cheat and directly exchange values without
* going through the trouble of calling a conversion function.
*
* This routine is also used to look for length-coercion functions, which
* are similar but accept a second argument. secondArgType is the type
* of the second argument (normally INT4OID), or InvalidOid if we are
* looking for a regular coercion function.
* As of 7.3, binary compatibility isn't hardwired into the code anymore.
* We consider two types binary-compatible if there is an implicit,
* no-function-needed pg_cast entry. NOTE that we assume that such
* entries are symmetric, ie, it doesn't matter which type we consider
* source and which target. (cf. checks in opr_sanity regression test)
*/
bool
IsBinaryCompatible
(
Oid
type1
,
Oid
type2
)
{
HeapTuple
tuple
;
Form_pg_cast
castForm
;
bool
result
;
/* Fast path if same type */
if
(
type1
==
type2
)
return
true
;
/* Perhaps the types are domains; if so, look at their base types */
if
(
OidIsValid
(
type1
))
type1
=
getBaseType
(
type1
);
if
(
OidIsValid
(
type2
))
type2
=
getBaseType
(
type2
);
/* Somewhat-fast path if same base type */
if
(
type1
==
type2
)
return
true
;
/* Else look in pg_cast */
tuple
=
SearchSysCache
(
CASTSOURCETARGET
,
ObjectIdGetDatum
(
type1
),
ObjectIdGetDatum
(
type2
),
0
,
0
);
if
(
!
HeapTupleIsValid
(
tuple
))
return
false
;
/* no cast */
castForm
=
(
Form_pg_cast
)
GETSTRUCT
(
tuple
);
result
=
(
castForm
->
castfunc
==
InvalidOid
)
&&
castForm
->
castimplicit
;
ReleaseSysCache
(
tuple
);
return
result
;
}
/*
* find_coercion_pathway
* Look for a coercion pathway between two types.
*
* If a function is found, return its pg_proc OID; else return InvalidOid.
* If we find a matching entry in pg_cast, return TRUE, and set *funcid
* to the castfunc value (which may be InvalidOid for a binary-compatible
* coercion).
*/
static
Oid
find_coercion_function
(
Oid
targetTypeId
,
Oid
sourceTypeId
,
bool
isExplicit
)
static
bool
find_coercion_pathway
(
Oid
targetTypeId
,
Oid
sourceTypeId
,
bool
isExplicit
,
Oid
*
funcid
)
{
Oid
funcid
=
InvalidOid
;
bool
result
=
false
;
HeapTuple
tuple
;
*
funcid
=
InvalidOid
;
/* Perhaps the types are domains; if so, look at their base types */
if
(
OidIsValid
(
sourceTypeId
))
sourceTypeId
=
getBaseType
(
sourceTypeId
);
if
(
OidIsValid
(
targetTypeId
))
targetTypeId
=
getBaseType
(
targetTypeId
);
/* Domains are automatically binary-compatible with their base type */
if
(
sourceTypeId
==
targetTypeId
)
return
true
;
/* Else look in pg_cast */
tuple
=
SearchSysCache
(
CASTSOURCETARGET
,
ObjectIdGetDatum
(
sourceTypeId
),
ObjectIdGetDatum
(
targetTypeId
),
...
...
@@ -799,18 +818,36 @@ find_coercion_function(Oid targetTypeId, Oid sourceTypeId, bool isExplicit)
if
(
HeapTupleIsValid
(
tuple
))
{
Form_pg_cast
c
f
orm
=
(
Form_pg_cast
)
GETSTRUCT
(
tuple
);
Form_pg_cast
c
astF
orm
=
(
Form_pg_cast
)
GETSTRUCT
(
tuple
);
if
(
isExplicit
||
cform
->
castimplicit
)
funcid
=
cform
->
castfunc
;
if
(
isExplicit
||
castForm
->
castimplicit
)
{
*
funcid
=
castForm
->
castfunc
;
result
=
true
;
}
ReleaseSysCache
(
tuple
);
}
return
funcid
;
return
result
;
}
/*
* find_typmod_coercion_function -- does the given type need length coercion?
*
* If the target type possesses a function named for the type
* and having parameter signature (targettype, int4), we assume that
* the type requires coercion to its own length and that the said
* function should be invoked to do that.
*
* "bpchar" (ie, char(N)) and "numeric" are examples of such types.
*
* This mechanism may seem pretty grotty and in need of replacement by
* something in pg_cast, but since typmod is only interesting for datatypes
* that have special handling in the grammar, there's not really much
* percentage in making it any easier to apply such coercions ...
*/
static
Oid
find_typmod_coercion_function
(
Oid
typeId
)
{
...
...
@@ -849,6 +886,7 @@ find_typmod_coercion_function(Oid typeId)
}
ReleaseSysCache
(
targetType
);
return
funcid
;
}
...
...
@@ -877,74 +915,3 @@ build_func_call(Oid funcid, Oid rettype, List *args)
return
(
Node
*
)
expr
;
}
/*
* Create an expression tree to enforce the constraints (if any)
* that should be applied by the type. Currently this is only
* interesting for domain types.
*/
Node
*
coerce_type_constraints
(
ParseState
*
pstate
,
Node
*
arg
,
Oid
typeId
,
bool
applyTypmod
)
{
char
*
notNull
=
NULL
;
int32
typmod
=
-
1
;
for
(;;)
{
HeapTuple
tup
;
Form_pg_type
typTup
;
tup
=
SearchSysCache
(
TYPEOID
,
ObjectIdGetDatum
(
typeId
),
0
,
0
,
0
);
if
(
!
HeapTupleIsValid
(
tup
))
elog
(
ERROR
,
"coerce_type_constraints: failed to lookup type %u"
,
typeId
);
typTup
=
(
Form_pg_type
)
GETSTRUCT
(
tup
);
/* Test for NOT NULL Constraint */
if
(
typTup
->
typnotnull
&&
notNull
==
NULL
)
notNull
=
pstrdup
(
NameStr
(
typTup
->
typname
));
/* TODO: Add CHECK Constraints to domains */
if
(
typTup
->
typtype
!=
'd'
)
{
/* Not a domain, so done */
ReleaseSysCache
(
tup
);
break
;
}
Assert
(
typmod
<
0
);
typeId
=
typTup
->
typbasetype
;
typmod
=
typTup
->
typtypmod
;
ReleaseSysCache
(
tup
);
}
/*
* If domain applies a typmod to its base type, do length coercion.
*/
if
(
applyTypmod
&&
typmod
>=
0
)
arg
=
coerce_type_typmod
(
pstate
,
arg
,
typeId
,
typmod
);
/*
* Only need to add one NOT NULL check regardless of how many
* domains in the stack request it. The topmost domain that
* requested it is used as the constraint name.
*/
if
(
notNull
)
{
ConstraintTest
*
r
=
makeNode
(
ConstraintTest
);
r
->
arg
=
arg
;
r
->
testtype
=
CONSTR_TEST_NOTNULL
;
r
->
name
=
notNull
;
r
->
check_expr
=
NULL
;
arg
=
(
Node
*
)
r
;
}
return
arg
;
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录