如何在返回 SELF 的 pl/sql 对象类型的函数中链接调用

Posted

技术标签:

【中文标题】如何在返回 SELF 的 pl/sql 对象类型的函数中链接调用【英文标题】:How to chain calls in a pl/sql object type of functions returning SELF 【发布时间】:2013-08-06 17:23:58 【问题描述】:

我想让一个 oracle 对象返回自身并能够链接这些调用。我该怎么做?

我尝试返回相同的类型,但它不起作用,我还尝试添加一个由函数调用的过程,但它也不起作用。总是抱怨修改宽度成员的值。看起来函数不会承认副作用?,它们是按照更数学的函数原理建模的吗?这可以实现吗?我想我可以编写这个函数,让它用 SELF 构建一个新的矩形,但工作量太大了。

我的目标是能够像 jQuery 或一些 java 类(单例?)这样链接调用。类似的东西:

r := r.setWidth(0).setWidth(1).setWidth(2);

当然,它会有更多的方法,它不会是一个矩形。这是错误:

Error: PLS-00363: expression 'SELF' cannot be used as an assignment target
Line: 18
Text: stWidth(w);

-

CREATE OR REPLACE TYPE rectangle AS OBJECT
(
-- The type has 3 attributes.
  length NUMBER,
  width NUMBER,
  area NUMBER,
-- Define a constructor that has only 2 parameters.
  CONSTRUCTOR FUNCTION rectangle(length NUMBER, width NUMBER)
    RETURN SELF AS RESULT,
  MEMBER FUNCTION setWidth(w NUMBER) RETURN rectangle,
  MEMBER PROCEDURE stWidth(w NUMBER)
)

-

CREATE OR REPLACE TYPE BODY rectangle AS
  CONSTRUCTOR FUNCTION rectangle(length NUMBER, width NUMBER)
    RETURN SELF AS RESULT
  AS
  BEGIN
    SELF.length := length;
    SELF.width := width;
-- We compute the area rather than accepting it as a parameter.
    SELF.area := length * width;
    RETURN;
  END;
  MEMBER PROCEDURE stWidth(w NUMBER) IS
  BEGIN
    self.width := w;
  END;
  MEMBER FUNCTION setWidth(w NUMBER) RETURN rectangle IS
    BEGIN
        stWidth(w);

        RETURN SELF;
  END;
END;

提前致谢。

【问题讨论】:

【参考方案1】:

您不能同时更改对象和分配给它。您已经知道解决方案“使用 SELF 构建一个新矩形”。但这不会是很多工作。

替换这个:

  MEMBER FUNCTION setWidth(w NUMBER) RETURN rectangle IS
    BEGIN
        stWidth(w);
        RETURN SELF;
  END;

用这个:

  MEMBER FUNCTION setWidth(w NUMBER) RETURN rectangle IS
      v_rectangle rectangle := self;
    BEGIN
        v_rectangle.width := w;
        RETURN v_rectangle;
  END;

您实际上遇到了编译错误。默认情况下,SELF 是一个IN 参数。对stWidth 的调用失败,因为它正在使用self.width := w; 修改IN 参数。

见:http://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjbas.htm#CHDCFEEE

SELF 始终是传递给方法的第一个参数。

在成员函数中,如果没有声明SELF,它的参数模式 默认为 IN。

在成员过程中,如果没有声明SELF,它的参数模式 默认为 IN OUT。默认行为不包括 NOCOPY 编译器提示。

【讨论】:

你知道这种方法的内存打印吗?每次通话后都会清理自我吗?还是制作一个采用另一个矩形并复制属性的构造函数更好?看起来 SELF 作为原始对象的副本传递,这让我认为您的方法是完美的,我该如何确定?。 我接受并编辑了答案,希望您不要介意第二个。那是关键!,我期待 SELF 在程序和函数中表现相同;我不知道 SELF 是作为参数传递的,而不是像其他语言中的“this”这样的范围内的对象,我也尝试将 RETURN SELF 作为函数签名,但这也不起作用。非常感谢。 内存似乎没问题。我循环浏览了您的示例 1000 万次 - 它在 18 秒内运行,仅使用了 3MB 和 2MB 的“会话 pga 内存最大值”和“会话 uga 内存最大值”。感谢您的编辑。【参考方案2】:

对不起,我来晚了,但是其他的答案都不正确,至少使用Oracle 11gR2,你想要实现的目标确实是完全可能的。

我最近偶然发现了这个问题,我发现可以完全按照您的尝试返回对 SELF 的引用,而无需进行任何权衡或应用变通方法。

唯一需要做的就是通过(显式)将(隐式)SELF 参数设置为SELF IN OUT rectangle 来重新定义方法。SELF 是静默传递给每个对象方法的主要参数,函数定义为 IN(不可变;这可能是编译器抱怨的原因)。这是在编译时建立的,但好的部分是当调用该方法时你可以省略它。 在文章末尾的示例中(根据您的稍作改写),我们定义了一个

MEMBER FUNCTION incrementWidth(SELF IN OUT rectangle, w NUMBER) RETURN rectangle

我们执行它忽略SELF 参考:

declare
  r rectangle := rectangle(1,2);
begin

  dbms_output.put_line('width is: ' || r.width);
  dbms_output.put_line('new width is: ' || r.incrementWidth(3).width);

end;
/

请注意,有两个警告需要注意。

警告 1

每个方法调用都会临时创建对象的新副本。 但只是暂时的,新实例是短暂的,就在方法的开始和结束之间。 这是在所有函数或过程上使用IN OUT 参数所固有的,并不特定于对象类型。如果您想阻止这种行为,您可能需要使用 NOCOPY 提示重新定义函数的签名:

MEMBER FUNCTION incrementWidth(SELF IN OUT NOCOPY rectangle, w NUMBER) RETURN rectangle

请参阅ORACLE-BASE - NOCOPY 了解更多详情。 请注意,这是一个提示,但这并不能保证您最终使用的是相同的对象引用而不是新创建的对象,因此请谨慎使用。

警告 2

鉴于您提出了这个问题,您很有可能具有 OOP 背景,并且在尝试调用该方法而不使用返回的引用时可能会感到惊讶

r.incrementWidth(10);

编译器会返回错误:

PLS-00221: 'INCREMENTWIDTH' is not a procedure or is undefined

那么这里发生了什么?好吧,pl/sql 中所谓的“静态多态”(即编译过程中方法重载的选择)与其他 OOP 语言略有不同,因为它甚至考虑了 RETURNed 类型的使用。为了解决这个问题,添加一个带有签名的伴随过程,其区别在于缺少返回类型:

MEMBER FUNCTION incrementWidth(SELF IN OUT rectangle, w NUMBER) RETURN rectangle,
MEMBER PROCEDURE incrementWidth(SELF IN OUT rectangle, w NUMBER)

合理地,如果您不想在函数和过程中重复相同的代码,过程将在内部委托函数;并且根据您使用的 Oracle 版本,您可能希望使用代码内联(请参阅OCP: More New PL/SQL Features)以达到与复制粘贴实现相同的速度(您几乎不会注意到真正的区别)。显式“内联”按名称指向方法,但是在方法名称被重载的情况下它也可以工作。

在下面的示例中,您将看到根据返回/未返回参数的使用情况交替调用函数或过程。

终于...

可能想写的代码如下(我没有使用NOCOPY 不是为了污染相关的东西,但这样做很简单)

CREATE OR REPLACE TYPE rectangle AS OBJECT
(
  length NUMBER,
  width NUMBER,
  CONSTRUCTOR FUNCTION rectangle(length NUMBER, width NUMBER)
    RETURN SELF AS RESULT,
  MEMBER FUNCTION incrementWidth(SELF IN OUT rectangle, w NUMBER) RETURN rectangle,
  MEMBER PROCEDURE incrementWidth(SELF IN OUT rectangle, w NUMBER)
);
/

CREATE OR REPLACE TYPE BODY rectangle AS
  CONSTRUCTOR FUNCTION rectangle(length NUMBER, width NUMBER)
    RETURN SELF AS RESULT
  AS
  BEGIN
    SELF.length := length;
    SELF.width := width;
    RETURN;
  END;
  MEMBER FUNCTION incrementWidth(SELF IN OUT rectangle, w NUMBER) RETURN rectangle IS
    BEGIN
        dbms_output.put_line('...invoking the function with input ' || w);
        width := width + w;
        RETURN SELF;
  END;
  MEMBER PROCEDURE incrementWidth(SELF IN OUT rectangle, w NUMBER) IS
    BEGIN
        PRAGMA INLINE (incrementWidth, 'YES');
        dbms_output.put_line('...invoking the procedure with input ' || w || ', that in turn is...');
        self := incrementWidth(w);
  END;
END;
/

执行...

set serveroutput on
select * from v$version where rownum = 1;

declare
  r rectangle := rectangle(1,2);
begin

  dbms_output.put_line('width is: ' || r.width);

  --this is invoking the "function" version, because we are making use of
  --the returned rectangle object
  dbms_output.put_line('new width is: ' || r.incrementWidth(3).width);

  --the original reference has been updated even without using the NO COPY hint
  dbms_output.put_line('original object has width updated: ' || r.width);

  --this is invoking the "procedure" version, because we are not using the returned object
  r.incrementWidth(3);

  --of course this has finally worked as well
  dbms_output.put_line('again what is the new width like now?: ' || r.width);

end;

/

你得到

BANNER                                                                     
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production  

width is: 2
...invoking the function with input 3
new width is: 5
original object has width updated: 5
...invoking the procedure with input 3, that in turn is...
...invoking the function with input 3
again what is the new width like now?: 8

【讨论】:

【参考方案3】:

您不能从成员函数返回 SELF。您可以创建一个副本,但我不确定为什么 setwidth 函数会返回一个矩形对象。我知道这个基本示例可能来自一些旧的 Oracle 文档,但我不会将计算字段(区域)作为属性。如果它被计算,它应该是一个成员函数(或过程)。原因是除非您想始终记住在影响区域的函数中更新区域,否则您将自取其辱。您的示例已损坏(除了返回 self 的 setWidth),因为只有构造函数计算面积

set serveroutput on
declare
  r rectangle := rectangle(3,2);
begin
  dbms_output.put_line('Area is: ' || r.area);
  -- change the width
  r.stWidth(4);
  dbms_output.put_line('Area is: ' || r.area);
end;

输出:

Area is: 6
Area is: 6

显然这是不正确的。我会做类似的事情:

CREATE OR REPLACE TYPE rectangle AS OBJECT
(
-- The type has 2 attributes.
  len NUMBER,
  width NUMBER,
-- Define a constructor that has only 2 parameters.
  CONSTRUCTOR FUNCTION rectangle(len NUMBER, width NUMBER)
    RETURN SELF AS RESULT,
  MEMBER PROCEDURE setLength(l NUMBER),
  MEMBER PROCEDURE setWidth(w NUMBER),
  MEMBER FUNCTION getArea return NUMBER,
  MEMBER PROCEDURE showArea
);


CREATE OR REPLACE TYPE BODY rectangle AS
  CONSTRUCTOR FUNCTION rectangle(len NUMBER, width NUMBER)
    RETURN SELF AS RESULT
  AS
  BEGIN
    SELF.len := len;
    SELF.width := width;
    RETURN;
  END;

  MEMBER PROCEDURE setLength(l NUMBER) IS
  BEGIN
    self.len := l;

  END;
  MEMBER PROCEDURE setWidth(w NUMBER) IS
  BEGIN
    self.width := w;

  END;

  MEMBER FUNCTION getArea return NUMBER IS
  BEGIN
    return self.len * self.width;
  END;

  MEMBER PROCEDURE showArea IS
  BEGIN
    -- Just shows how we calculated area and spits to console
    dbms_output.put_line('Area is: ' || self.getArea || ' (' || self.len || ' * ' || self.width || ')');
  END;
END;

这样你就有了:

set serveroutput on
declare
  r rectangle := rectangle(3,2);
begin
  dbms_output.put_line('Area is: ' || r.getArea);
  -- change the width
  r.setWidth(4);
  dbms_output.put_line('Area is: ' || r.getArea);
end;

输出:

Area is: 6
Area is: 12

【讨论】:

您的猜测是正确的,这来自一个 Oracle 示例,我曾经看到链接函数调用的可能性。我发布的示例是来自 oracle 文档的“原样”,并使用应返回相同矩形的附加函数进行了修改。但是,我的问题的重点在于链接调用,除了评论之外,您并没有真正解决这些调用。我需要的对象甚至不是一个矩形,这只是一个例子。感谢您的回答和对计算字段的洞察力,我同意并实践这一点。【参考方案4】:

我也遇到了同样的问题,因为上面的答案不一致,我不得不自己试验。

尽管安东尼奥的回答是这样,但我有以下代码不起作用:

create or replace type tst as object (
  inner_value varchar2(100),

  constructor function tst return self as result,

  member function update_value(
    self in out nocopy tst, 
    p_value varchar2)
  return tst,

  member procedure print_value(self in out nocopy tst)

) final;
/

create or replace type body tst as
  constructor function tst return self as result
  is
  begin
    self.inner_value := 'DEFAULT';
    return;
  end;

  member function update_value(
    self in out nocopy tst, 
    p_value varchar2)
  return tst
  is
  begin
    self.inner_value := p_value;
    return self;
  end;

  member procedure print_value(self in out nocopy tst)
  is
  begin
    dbms_output.put_line(self.inner_value);
  end;

end;
/

无论self参数是否用nocopy定义,以下代码都不会运行。

set serveroutput on;
begin
  tst().update_value('TEST').print_value;
end;
/

如果将print_value 重新定义为member procedure print_value(self in tst),则tst().print_value; 成为有效语句。

如果self参数是in outin out nocopy,则构造函数不能被链接,无论你返回一个副本还是self,你总是会得到一个PLS-00363。对构造函数运行嵌套调用的唯一方法是在任何地方使用self in 参数并进行复制,这令人失望:

create or replace type tst as object (
  inner_value varchar2(100),

  constructor function tst return self as result,

  member function update_value(
    self in tst, 
    p_value varchar2)
  return tst,

  member procedure print_value(self in tst)

) final;
/

create or replace type body tst as
  constructor function tst return self as result
  is
  begin
    self.inner_value := 'DEFAULT';
    return;
  end;

  member function update_value(
    self in tst, 
    p_value varchar2)
  return tst
  is
    a_copy tst;
  begin
    a_copy := tst;
    a_copy.inner_value := p_value;
    return a_copy;
  end;

  member procedure print_value(self in tst)
  is
  begin
    dbms_output.put_line(self.inner_value);
  end;

end;
/

然后按预期打印TEST

set serveroutput on;
begin
  tst().update_value('TEST').print_value;
end;
/

如果您可以将语句拆分为t := tst(); t.update_value('TEST').print_value;,那么它甚至可以使用in out nocopy self 参数。

【讨论】:

以上是关于如何在返回 SELF 的 pl/sql 对象类型的函数中链接调用的主要内容,如果未能解决你的问题,请参考以下文章

ORA-06504: PL/SQL: 执行时返回结果集变量的类型

PL/SQL 继承实现,其中过程在子对象下实现

返回表查询的 PL/SQL 封装函数

EXECUTE IMMEDIATE PL/SQL 块返回类型

从 PL/SQL 函数返回表 - 不正确的数据类型

如何编写 PL 以返回通过服务总线中的 DB 适配器自动生成强类型 XSD 的多行? (甲骨文 PL/SQL)