Search code examples
oracle-databasefunctionplsqltypesmember

How to Chain Member Functions in a PL/SQL Object Type


Perhaps this is a poor use case for what I'm trying to accomplish, but I've read through dozens of pages and I can't figure out if this is possible in Oracle or not.

I would like to accomplish something similar to another stackoverflow question: How to chain calls in a pl/sql object type of functions returning SELF

In my code, I have a type named parseName with a constructor function and 3 member functions: getFirstName, getMiddleName, getLastName. I would like to have a fourth member function called: cleanString which removes all characters not between A-Z or a-z.

Example calls:

SELECT parseName('John123 Doe').getFirstName().cleanString()
FROM dual;

SELECT parseName('John123 Doe').getFirstName()
FROM dual;

Here's the type as I have it written so far.

CREATE OR REPLACE TYPE parseName AS OBJECT
(
    g_name         VARCHAR2(255),
    g_parts        NUMBER,
    g_first_name   VARCHAR2(255),
    g_middle_name  VARCHAR2(1),
    g_last_name    VARCHAR2(255),
    -- constructor function
    CONSTRUCTOR FUNCTION parseName
      (p_name IN VARCHAR2)
      RETURN self AS result,
    -- member functions
    MEMBER FUNCTION getFirstName  RETURN VARCHAR2,
    MEMBER FUNCTION getMiddleName RETURN VARCHAR2,
    MEMBER FUNCTION getLastName   RETURN VARCHAR2
);
/
SHOW ERRORS
/

CREATE OR REPLACE TYPE BODY parseName IS

  -- populateValues
  CONSTRUCTOR FUNCTION parseName
    (p_name IN VARCHAR2)
  RETURN self AS result
  IS
    -- other variables
    v_name    VARCHAR2(255);
    v_length  NUMBER;
    v_parts   NUMBER;
    v_instr   NUMBER;
  BEGIN

    -- save off input
    v_name := TRIM(p_name);

    -- check
    IF v_name IS NULL THEN
      self.g_first_name  := 'Unknown';
      self.g_middle_name := ' ';
      self.g_last_name   := 'Unknown';
      RETURN;
    END IF;

    -- otherwise, fill our global
    self.g_name := v_name;

    -- exit
    RETURN;

  END;

  /* getFirstName */
  /* --------------------------------------- */
  MEMBER FUNCTION getFirstName
    RETURN VARCHAR2
  IS
    v_parts NUMBER;
  BEGIN

    -- did we get a null on construct?
    IF self.g_first_name IS NOT NULL THEN
      RETURN self.g_first_name;
    END IF;

    -- how many spaces do we have?
    v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));

    -- if 0 spaces, return the name
    IF v_parts = 0 THEN
      RETURN self.g_name;
    -- else, return everything up to the space
    ELSE
      RETURN TRIM(SUBSTR(self.g_name,1, INSTR(self.g_name,' ',1) ));
    END IF;

  END getFirstName;

  /* getMiddleName */
  /* --------------------------------------- */
  MEMBER FUNCTION getMiddleName
    RETURN VARCHAR2
  IS
    v_parts  NUMBER;
    v_instr2 NUMBER;
    v_instr1 NUMBER;
  BEGIN

    -- did we get a null on construct?
    IF self.g_middle_name IS NOT NULL THEN
      RETURN NULL;
    END IF;

    -- how many spaces do we have?
    v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));

    -- if we have zero spaces, we only have a first name, return null
    IF v_parts = 0 THEN
      RETURN NULL;
    -- don't do middle if we only have 1 space
    ELSIF v_parts = 1 THEN
      RETURN NULL;
    -- else, we've got more than one, so grab between space 1 and 2
    ELSE
      v_instr2 := INSTR(self.g_name,' ',1,2);
      v_instr1 := INSTR(self.g_name,' ',1,1);
      RETURN TRIM( SUBSTR(self.g_name, v_instr1, (v_instr2-v_instr1) ));
    END IF;

  END getMiddleName;

  /* getLastName */
  /* --------------------------------------- */
  MEMBER FUNCTION getLastName
    RETURN VARCHAR2
  IS
    v_parts  NUMBER;
  BEGIN

    -- did we get a null on construct?
    IF self.g_last_name IS NOT NULL THEN
      RETURN self.g_last_name;
    END IF;

    -- how many spaces do we have?
    v_parts := LENGTH(self.g_name) - LENGTH(REPLACE(self.g_name,' ',''));

    -- if we have zero spaces, we only have a first name, return 'Unknown'
    IF v_parts = 0 THEN
      RETURN 'Unknown';
    -- if have 1 space, the space on is the last name
    ELSIF v_parts = 1 THEN
      RETURN TRIM( SUBSTR(self.g_name, INSTR(self.g_name,' ',1,1), LENGTH(self.g_name)) );
    -- else, we've got more than one, go from 2 to end
    ELSE
      RETURN TRIM( SUBSTR(self.g_name, INSTR(self.g_name,' ',1,2), LENGTH(self.g_name)) );
    END IF;

  END getLastName;


END;
/
SHOW ERRORS
/

.

Thanks for any advice that you can provide.


Solution

  • I would like to have a fourth member function called: cleanString which removes all characters not between A-Z or a-z.

    Example calls:

    SELECT parseName('John123 Doe').getFirstName().cleanString()
    FROM dual;
    

    When you call .getFirstName() the expectation is that it returns the person's first name (after all that is what the member function's name says) and the name is a VARCHAR2 data type. VARCHAR2 is a primitive data type and is not an object that you could (somehow) extend to have a .cleanString() member function.

    You can either define a cleanString() function:

    CREATE FUNCTION cleanString( value VARCHAR2 ) RETURN VARCHAR2
    IS
    BEGIN
      RETURN REGEXP_REPLACE( value, '[^[:alpha:]]+' );
    END cleanString;
    /
    

    And then call:

     SELECT cleanString( parseName('John123 Doe').getFirstName() )
     FROM   DUAL;
    

    Or you could make a member function that cleans the names before you return them:

    SQL Fiddle

    Oracle 11g R2 Schema Setup:

    CREATE OR REPLACE TYPE parseName AS OBJECT
    (
        g_name         VARCHAR2(255),
        g_first_name   VARCHAR2(255),
        g_middle_name  VARCHAR2(1),
        g_last_name    VARCHAR2(255),
        -- constructor function
        CONSTRUCTOR FUNCTION parseName(p_name IN VARCHAR2) RETURN self AS result,
        -- member functions
        MEMBER FUNCTION cleanNames    RETURN parseName,
        MEMBER FUNCTION getFirstName  RETURN VARCHAR2,
        MEMBER FUNCTION getMiddleName RETURN VARCHAR2,
        MEMBER FUNCTION getLastName   RETURN VARCHAR2
    );
    /
    
    CREATE OR REPLACE TYPE BODY parseName IS
      -- populateValues
      CONSTRUCTOR FUNCTION parseName(p_name IN VARCHAR2) RETURN self AS result
      IS
      BEGIN
        g_name := TRIM(p_name);
        g_first_name  := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 1 );
        g_middle_name := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 3 );
        g_last_name   := REGEXP_SUBSTR( g_name, '^(\S+)\s+((\S*?)\s+)?(.*)$', 1, 1, NULL, 4 );
        RETURN;
      END;
    
      MEMBER FUNCTION cleanNames RETURN parseName
      IS
        v_name parseName := SELF;
      BEGIN
        v_name.g_first_name  := cleanString( SELF.g_first_name );
        v_name.g_middle_name := cleanString( SELF.g_middle_name );
        v_name.g_last_name   := cleanString( SELF.g_last_name );
        RETURN v_name;
      END;
    
      MEMBER FUNCTION getFirstName RETURN VARCHAR2
      IS
      BEGIN
        RETURN g_first_name;
      END getFirstName;
    
      MEMBER FUNCTION getMiddleName RETURN VARCHAR2
      IS
      BEGIN
        RETURN g_middle_name;
      END getMiddleName;
    
      MEMBER FUNCTION getLastName RETURN VARCHAR2
      IS
      BEGIN
        RETURN g_last_name;
      END getLastName;
    END;
    /
    

    Query 1:

    SELECT parseName('John123 Doe').getFirstName(),
           parseName('John123 Doe').cleanNames().getFirstName()
    FROM DUAL
    

    Results:

    | PARSENAME('JOHN123DOE').GETFIRSTNAME() | PARSENAME('JOHN123DOE').CLEANNAMES().GETFIRSTNAME() |
    |----------------------------------------|-----------------------------------------------------|
    |                                John123 |                                                John |