例如,我想制作自己的布尔类型并将其称为Bool。我该如何做?
或者,一个交通信号灯类型,即只有红色、黄色、绿色(当然还有null)的类型。
或者,一个交通信号灯类型,即只有红色、黄色、绿色(当然还有null)的类型。
我不认为由 A.B.Cade 提供的解决方案完全正确。假设我们有以下过程:
procedure TestEnum(enum_in lights);
enum_in
的值是什么?红色? 黄色? 绿色?
我提出另一种解决方案。这里是一个示例包。
CREATE OR REPLACE PACKAGE pkg_test_enum IS
SUBTYPE TLight IS BINARY_INTEGER RANGE 0..2;
Red CONSTANT TLight := 0;
Yellow CONSTANT TLight := 1;
Green CONSTANT TLight := 2;
--get sting name for my "enum" type
FUNCTION GetLightValueName(enum_in TLight) RETURN VARCHAR2;
PROCEDURE EnumTest(enum_in TLight);
END pkg_test_enum;
CREATE OR REPLACE PACKAGE BODY pkg_test_enum IS
FUNCTION GetLightValueName(enum_in TLight)
RETURN VARCHAR2
IS
ResultValue VARCHAR2(6);
BEGIN
CASE enum_in
WHEN Red THEN ResultValue := 'Red';
WHEN Green THEN ResultValue := 'Green';
WHEN Yellow THEN ResultValue := 'Yellow';
ELSE ResultValue := '';
END CASE;
RETURN ResultValue;
END GetLightValueName;
PROCEDURE EnumTest(enum_in TLight)
IS
BEGIN
--do stuff
NULL;
END EnumTest;
END pkg_test_enum;
我现在可以在不同的包中使用TLight
。我现在可以针对预定义值或null测试enum_in
。
以下是使用示例:
begin
pkg_test_enum.EnumTest(pkg_test_enum.Red);
end;
另外,您可以将此类型设置为非空。
SUBTYPE TLight 是二进制整数,范围为0..2,并且不能为空;
除了常量之外,该博客还定义了一种有效颜色的子类型。
SQL> declare
2 RED constant number(1):=1;
3 GREEN constant number(1):=2;
4 BLUE constant number(1):=3;
5 YELLOW constant number(1):=4;
6 --
7 VIOLET constant number(1):=7;
8 --
9 subtype colors is binary_integer range 1..4;
10 --
11 pv_var colors;
12 --
13 function test_a (pv_var1 in colors) return colors
14 is
15 begin
16 if(pv_var1 = YELLOW) then
17 return(BLUE);
18 else
19 return(RED);
20 end if;
21 end;
22 --
create or replace type lights as object
(
red varchar2(8),
yellow varchar2(8),
green varchar2(8),
constructor function lights return self as result
)
以及正文:
create or replace type body lights is
constructor function lights return self as result is
begin
self.red = 'red';
self.yellow = 'yellow';
self.green = 'green';
return;
end;
end;
然后在代码中你可以使用它:
declare
l lights := new lights;
begin
dbms_output.put_line(l.red);
end;
我之前使用了与@mydogtom和@klas-lindbäck相同的方法。当我试图刷新记忆时,我发现了这个方法。然而,@a-b-cade建议的对象方法让我思考。我同意@mydogtom所描述的问题(什么是值?),但是使用对象是否可能呢。
我想出了一种方法,它使用一个具有单个成员属性的对象来表示枚举的值,并为每个可能的值提供静态函数。我无法看到如何与子类型结合使用以对值字段进行真正的限制,也不能正式地使其非空。但是,我们可以在构造函数中验证它。与“正确”的枚举(例如Java中的枚举)相比,功能上的缺点是我们无法阻止某人直接将val
属性更新为无效值。但只要人们使用构造函数和set_value函数,就是安全的。我不确定开销(包括运行时创建对象和维护对象等方面)是否值得,因此我可能会继续使用@mydogtom描述的方法,但我不确定。
set_value
中设置(类似于@a-b-cade的版本),但这会添加另一个可以直接更新的属性,因此会出现另一组状态,其中val和name不匹配,因此我更喜欢使用名称作为函数的方法。demo_enum
类型的示例用法:procedure do_stuff(enum in demo_enum) is
begin
if enum.eqals(demo_enum.foo()) then
-- do something
end if;
end do_stuff;
或者
procedure do_stuff(enum1 in demo_enum, enum2 in demo_enum) is
begin
if enum1.eqals(enum2) then
-- do something
end if;
end do_stuff;
val
字段、静态值的equals
函数、set_value
和to_string
函数。还有name
函数,但这只是被覆盖(无法正式使成员函数抽象,因此基本版本只会抛出异常)。我也使用name
作为检查值是否有效的方式,以减少需要枚举可能值的位置数量。create or replace type enum_base as object(
-- member field to store actual value
val integer,
-- Essentially abstract name function
-- Should be overridden to return name based on value
-- Should throw exception for null or invalid values
member function name return varchar2,
--
-- Used to update the value. Called by constructor
--
member procedure set_value(pvalue in integer),
--
-- Checks the current value is valid
-- Since we can't stop someone updating the val property directly, you can supply invalid values
--
member function isValid return boolean,
--
-- Checks for equality with integer value
-- E.g. with a static function for a possible value: enum_var.equals( my_enum_type.someval() )
--
member function equals(other in integer) return boolean,
--
-- For debugging, prints out name and value (to get just name, use name function)
--
member function to_string return varchar2
) not instantiable not final;
/
create or replace type body enum_base is
member function name return varchar2 is
begin
-- This function must be overriden in child enum classes.
-- Can't figure out how to do an abstract function, so just throw an error
raise invalid_number;
end;
member procedure set_value(pvalue in integer) is
vName varchar2(3);
begin
self.val := pvalue;
-- call name() in order to also validate that value is valid
vName := self.name;
end set_value;
member function isValid return boolean is
vName varchar2(3);
begin
begin
-- call name() in order to also validate that value is valid
vName := self.name;
return true;
exception
when others then
return false;
end;
end isValid;
member function equals(other in integer) return boolean is
begin
return self.val = other;
end equals;
member function to_string return varchar2 is
begin
if self.val is null then
return 'NULL';
end if;
return self.name || ' (' || self.val || ')';
end to_string;
end;
/
set_value
),并重写name
函数以返回每个可能值的名称。然后,我为每个可能的值定义一个静态函数,该函数返回该值的整数索引。最后,我定义了一个equals
的重载,将其与同一类型的另一个枚举进行比较。如果您想要将其他属性附加到每个值,则可以通过定义其他函数来实现。create or replace type demo_enum under enum_base (
-- Note: the name of the parameter in the constructor MUST be the same as the name of the variable.
-- Otherwise a "PLS-00307: too many declarations" error will be thrown when trying to instanciate
-- the object using this constructor
constructor function demo_enum(val in integer) return self as result,
--
-- Override name function from base to give name for each possible value and throw
-- exception for null/invalid values
--
overriding member function name return varchar2,
--
-- Check for equality with another enum object
--
member function equals(other in demo_enum) return boolean,
--
-- Define a function for each possible value
--
static function foo return integer,
static function bar return integer
) instantiable final;
/
create or replace type body demo_enum is
constructor function demo_enum(val in integer) return self as result is
begin
self.set_value(val);
return;
end demo_enum;
overriding member function name return varchar2 is
begin
if self.val is null then
raise invalid_number;
end if;
case self.val
when demo_enum.foo() then
return 'FOO';
when demo_enum.bar() then
return 'BAR';
else
raise case_not_found;
end case;
end;
member function equals(other in demo_enum) return boolean is
begin
return self.val = other.val;
end equals;
static function foo return integer is
begin
return 0;
end foo;
static function bar return integer is
begin
return 1;
end bar;
end;
/
这可以进行测试。我定义了两组测试。一组是手动测试,用于测试此特定枚举类型,同时说明用法:
--
-- Manual tests of the various functions in the enum
--
declare
foo demo_enum := demo_enum(demo_enum.foo());
alsoFoo demo_enum := demo_enum(demo_enum.foo());
bar demo_enum := demo_enum(demo_enum.bar());
vName varchar2(100);
procedure assertEquals(a in varchar2, b in varchar2) is
begin
if a <> b then
raise invalid_number;
end if;
end assertEquals;
procedure assertEquals(a in boolean, b in boolean) is
begin
if a <> b then
raise invalid_number;
end if;
end assertEquals;
procedure test(vName in varchar2, enum in demo_enum, expectFoo in boolean) is
begin
dbms_output.put_line('Begin Test of ' || vName);
if enum.equals(demo_enum.foo()) then
dbms_output.put_line(vName || ' is foo');
assertEquals(expectFoo, true);
else
dbms_output.put_line(vName || ' is not foo');
assertEquals(expectFoo, false);
end if;
if enum.equals(demo_enum.bar()) then
dbms_output.put_line(vName || ' is bar');
assertEquals(expectFoo, false);
else
dbms_output.put_line(vName || ' is not bar');
assertEquals(expectFoo, true);
end if;
if enum.equals(foo) then
dbms_output.put_line(vName || '.equals(vFoo)');
assertEquals(expectFoo, true);
else
assertEquals(expectFoo, false);
end if;
if expectFoo then
assertEquals(enum.name, 'FOO');
else
assertEquals(enum.name, 'BAR');
end if;
assertEquals(enum.isValid, true);
case enum.val
when demo_enum.foo() then
dbms_output.put_line(vName || ' matches case foo');
when demo_enum.bar() then
dbms_output.put_line(vName || ' matches case bar');
else
dbms_output.put_line(vName || ' matches no case!!!');
end case;
dbms_output.put_line(vName || ': ' || enum.to_string());
dbms_output.put_line('--------------------------------------------------');
dbms_output.put_line('');
end test;
begin
test('foo', foo, true);
test('bar', bar, false);
test('alsoFoo', alsoFoo, true);
foo.val := -1;
assertEquals(foo.isValid, false);
begin
vName := foo.name;
exception
when case_not_found then
dbms_output.put_line('Correct exception for fetching name when invalid value: ' || sqlerrm);
end;
foo.val := null;
assertEquals(foo.isValid, false);
begin
vName := foo.name;
exception
when invalid_number then
dbms_output.put_line('Correct exception for fetching name when null value: ' || sqlerrm);
end;
end;
enum_base
的枚举类型(只要它不添加其他函数——如果有人知道如何仅查找静态函数,请告诉我)。这将检查您是否错误地将相同的整数值定义为多个可能值的静态函数:--
-- generated test that no two values are equal
--
declare
vSql varchar2(4000) := '';
typename constant varchar2(20) := 'demo_enum';
cursor posvals is
select procedure_name
from user_procedures
where object_name = upper(typename)
and procedure_name not in (upper(typename), 'EQUALS', 'NAME');
cursor posvals2 is
select procedure_name
from user_procedures
where object_name = upper(typename)
and procedure_name not in (upper(typename), 'EQUALS', 'NAME');
procedure addline(line in varchar2) is
begin
vSql := vSql || line || chr(10);
end;
begin
addline('declare');
addline(' enum ' || typename || ';');
addline('begin');
for posval in posvals loop
addline(' enum := ' || typename || '(' || typename || '.' || posval.procedure_name || '());');
for otherval in posvals2 loop
addline(' if enum.equals(' || typename || '.' || otherval.procedure_name || '()) then');
if otherval.procedure_name = posval.procedure_name then
addline(' dbms_output.put_line(''' || otherval.procedure_name || ' = ' || posval.procedure_name || ''');');
else
addline(' raise_application_error(-20000, ''' || otherval.procedure_name || ' = ' || posval.procedure_name || ''');');
end if;
addline(' else');
if otherval.procedure_name = posval.procedure_name then
addline(' raise_application_error(-20000, ''' || otherval.procedure_name || ' != ' || posval.procedure_name || ''');');
else
addline(' dbms_output.put_line(''' || otherval.procedure_name || ' != ' || posval.procedure_name || ''');');
end if;
addline(' end if;');
end loop;
addline('');
end loop;
addline('end;');
execute immediate vSql;
end;
CREATE OR REPLACE TYPE t_yes_no_enum AS OBJECT
( yes_no NUMBER
, CONSTRUCTOR FUNCTION t_yes_no_enum
( yes_no NUMBER
)
RETURN SELF AS RESULT
, STATIC FUNCTION yes
RETURN t_yes_no_enum
, STATIC FUNCTION no
RETURN t_yes_no_enum
, MAP MEMBER FUNCTION yes_no_map
RETURN NUMBER
) ;
/
CREATE OR REPLACE NONEDITIONABLE TYPE BODY t_yes_no_enum
AS
CONSTRUCTOR FUNCTION t_yes_no_enum
( yes_no NUMBER
)
RETURN SELF AS RESULT
IS
BEGIN
self.yes_no := yes_no ;
IF utl_call_stack.dynamic_depth > 1
AND utl_call_stack.unit_type(2) = 'TYPE BODY'
AND utl_call_stack.concatenate_subprogram(utl_call_stack.subprogram(2)) LIKE 'T_YES_NO_ENUM.%'
AND utl_call_stack.owner ( 2 ) = utl_call_stack.owner ( 1 )
THEN
NULL ;
ELSE
raise_application_error ( -20184 , 'Constructor calling prohibited. For object type creation use it''s static factory methods.' ) ;
END IF ;
RETURN ;
END ;
STATIC FUNCTION yes
RETURN t_yes_no_enum
IS
BEGIN
RETURN NEW t_yes_no_enum ( 1 ) ;
END ;
STATIC FUNCTION no
RETURN t_yes_no_enum
IS
BEGIN
RETURN NEW t_yes_no_enum ( 2 ) ;
END ;
MAP MEMBER FUNCTION yes_no_map
RETURN NUMBER
IS
BEGIN
IF coalesce ( self.yes_no , -999 ) NOT IN ( 1 , 2 ) THEN
raise_application_error ( -20185 , 'Changing object type attribute value prohibited.' ) ;
END IF ;
RETURN self.yes_no ;
END ;
END ;