firebird icon indicating copy to clipboard operation
firebird copied to clipboard

Consider the return value of deterministic functions to be invariant if all its arguments are invariant.

Open sim1984 opened this issue 2 years ago • 0 comments

Currently, the return result of deterministic functions is considered invariant only if the function contains no arguments. The implementation with a results cache storing <argument_values, result_value> pairs is quite complex. However, we can consider a simpler option: if the arguments of a deterministic function are invariants, such as literals or input parameters, then the return value of that function is also an invariant.

Simple example:

CREATE EXCEPTION E_INVALID_RACETIME_FORMAT '~Incorrect friskiness format~';

CREATE TABLE BIG_TABLE (
  ID BIGINT NOT NULL, 
  TIME_PASSED NUMERIC(18, 3),
  CONSTRAINT PK_BIG_TABLE PRIMARY KEY(ID)
);

SET TERM ^;

CREATE OR ALTER FUNCTION SF_RACETIME_TO_SEC (
    RACETIME VARCHAR(9))
RETURNS NUMERIC(18,3)
DETERMINISTIC
AS
DECLARE VARIABLE S VARCHAR(9);
DECLARE VARIABLE XCOMMAPOS SMALLINT;
DECLARE VARIABLE XDOTPOS SMALLINT;
DECLARE VARIABLE XMINUTE SMALLINT;
DECLARE VARIABLE XSECOND SMALLINT;
DECLARE VARIABLE XMS VARCHAR(9);
DECLARE VARIABLE XMILLISECOND SMALLINT;
DECLARE VARIABLE RTIME NUMERIC(18, 3);
BEGIN
  S = TRIM(RACETIME);
  S = TRIM('_' FROM S);
  RTIME = NULL;

  IF (TRIM(REPLACE(REPLACE(S, '.', ''), ',', '')) = '') THEN
    S = NULL;
  IF (S IS NOT NULL) THEN
  BEGIN
    IF (S NOT SIMILAR TO '[0-5]?[0-9].[0-5][0-9],[0-9]{1,3}') THEN
      EXCEPTION E_INVALID_RACETIME_FORMAT  '~Incorrect time format "' || RACETIME || '"~';

    XDOTPOS = POSITION('.', S);
    XCOMMAPOS = POSITION(',', S);
    XMINUTE = CAST(SUBSTRING(S FROM 1 FOR XDOTPOS) AS SMALLINT);

    XSECOND = CAST(SUBSTRING(S FROM XDOTPOS + 1 FOR XCOMMAPOS - XDOTPOS - 1) AS SMALLINT);

    XMS = TRIM(SUBSTRING(S FROM XCOMMAPOS + 1));
    XMILLISECOND = CAST(RPAD(XMS, 3, '0') AS SMALLINT);

    RTIME = 60 * XMINUTE + XSECOND + 0.001 * XMILLISECOND;
  END
  RETURN RTIME;
END^

EXECUTE BLOCK
AS
DECLARE ID BIGINT = 1;
BEGIN
  WHILE (ID <= 1000000) DO
  BEGIN
    INSERT INTO BIG_TABLE (ID, TIME_PASSED)
    VALUES (:ID, ROUND(150 - 50 * RAND(), 3));

    ID = ID + 1;
  END
END^

SET TERM ;^

COMMIT;
SELECT
COUNT(*)
FROM BIG_TABLE
WHERE BIG_TABLE.TIME_PASSED = SF_RACETIME_TO_SEC('02.01,6');
                COUNT
=====================
                   31

Current memory = 561798064
Delta memory = 358192
Max memory = 561813248
Elapsed time = 38.442 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 1015835

SQL> SELECT SF_RACETIME_TO_SEC('02.01,6') FROM RDB$DATABASE;

   SF_RACETIME_TO_SEC
=====================
              121.600

Current memory = 561794608
Delta memory = 10880
Max memory = 561884032
Elapsed time = 0.006 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 21
SELECT
COUNT(*)
FROM BIG_TABLE
WHERE BIG_TABLE.TIME_PASSED = 121.600;
                COUNT
=====================
                   31

Current memory = 561810448
Delta memory = 2784
Max memory = 561884032
Elapsed time = 0.393 sec
Buffers = 32768
Reads = 0
Writes = 0
Fetches = 1015569

If there is no index on the TIME_PASSED column, then SF_RACETIME_TO_SEC will be called for each record in the BIG_TABLE table.

sim1984 avatar Nov 21 '23 11:11 sim1984