Project: YugabyteDB (App Definition and Development / Database)
File: internal-representation-model.md

title: Modeling the internal representation of an interval value [YSQL] headerTitle: Modeling the internal representation and comparing the model with the actual implementation linkTitle: Representation model description: Demonstrates a model of the internal representation of an interval value using PL/pgSQL. [YSQL] menu: v2.18: identifier: internal-representation-model parent: interval-representation weight: 10 type: docs {{< tip title="Download and install the date-time utilities code." >}} The code on this page depends on the code presented in the section . This is included in the larger that includes all of the reusable code that the overall section describes and uses. {{< /tip >}} Each of the creates an interval value by specifying any subset of its parameterization using years, months, days, hours, minutes, and seconds from at least one of these through at most all six of them. (Omitting any of these six has the same effect as specifying zero for that parameter.) This section explains the algorithm that derives the fields of the [mm, dd, ss] representation tuple from the six years, months, days, hours, minutes, and seconds input values by modeling it using PL/pgSQL. The function intervalmmddss(intervalparameterizationt) accepts values for the subset of interest of the years, months, days, hours, minutes, and seconds parameterization that each of the ::interval typecast construction method and the makeinterval() built-in function construction method uses. It returns an intervalmmddsst instance. The implementation of the function therefore acts as the promised documentation of the algorithm. ```plpgsql drop function if exists intervalmmddss(intervalparameterization_t) cascade; create function intervalmmddss(p in intervalparameterization_t) returns intervalmmddsst language plpgsql as $body$ declare mmperyy constant double precision not null := 12.0; ddpermm constant double precision not null := 30.0; ssperdd constant double precision not null := 24.060.060.0; ssperhh constant double precision not null := 60.0*60.0; sspermi constant double precision not null := 60.0; mm_trunc constant int not null := trunc(p.mm); mmremainder constant double precision not null := p.mm - mmtrunc::double precision; -- This is a quirk. mmout constant int not null := trunc(p.yy*mmperyy) + mmtrunc; ddrealfrommm constant double precision not null := mmremainder*ddpermm; ddintfrommm constant int not null := trunc(ddrealfrommm); ddremainderfrommm constant double precision not null := ddrealfrommm - ddintfrom_mm::double precision; ddintfrom_user constant int not null := trunc(p.dd); ddremainderfromuser constant double precision not null := p.dd - ddintfromuser::double precision; ddout constant int not null := ddintfrommm + ddintfrom_user; dremainder constant double precision not null := ddremainderfrommm + ddremainderfrom_user; ssout constant double precision not null := dremainder*ssperdd + p.hh*ssperhh + p.mi*sspermi + p.ss; begin return (mmout, ddout, ssout)::intervalmmddss_t; end; $body$; ``` Notice how the quirks that the highlight are modeled. A fractional years part, after subtracting the integral part, carries "right" as INTEGRAL months. But, when there's a remainder after dividing this by 12, this does not carry further to days. A fractional months part, after subtracting the integral part, does carry "right" as REAL days. A fractional days part, after subtracting the integral part, carries "right" as REAL hours. The INTEGRAL days value is calculated by first truncating (a) the carry-over from months and (b) the truncation of the user-supplied value and then adding these. And the carry-over to hours is computed by adding the individually calculated remainders from steps (a) and (b). All this seems to be

But the need to do the calculation this way is pinpointed by the and ad hoc tests. The algorithm that this function implements was designed by humanly analyzing very many ad hoc observations like those shown above. The hypothesis that the algorithm embodies was tested as described below. Create a procedure to encapsulate the assertion that the rules that govern the simulation are the same as the rules that govern the actual implementation and a procedure to call this basic assert encapsulation with a range of parameterizxations. ```plpgsql drop procedure if exists assertmodelok(intervalparameterizationt) cascade; create procedure assertmodelok(p in intervalparameterizationt) language plpgsql as $body$ declare imodeled constant intervalmmddsst not null := intervalmmddss(p); ifrommodeled constant interval not null := intervalvalue(imodeled); iactual constant interval not null := intervalvalue(p); mmddssactual constant intervalmmddsst not null := intervalmmddss(i_actual); pmodeled constant intervalparameterizationt not null := parameterization(imodeled); pactual constant intervalparameterizationt not null := parameterization(iactual); begin -- Belt-and-braces check for mutual consistency among the "interval" utilities. assert (imodeled ~= mmddssactual), 'assert #1 failed'; assert (pmodeled ~= pactual ), 'assert #2 failed'; assert (ifrommodeled == i_actual ), 'assert #3 failed'; end; $body$; ``` Now test it on a set of input values that includes those used for the ad hoc tests shown above. ```plpgsql drop procedure if exists testinternalintervalrepresentationmodel() cascade; create procedure testinternalintervalrepresentationmodel() language plpgsql as $body$ begin call assertmodelok(interval_parameterization()); call assertmodelok(interval_parameterization( mm => 99, dd => 700, ss => 83987851.522816)); call assertmodelok(interval_parameterization( yy => 3.853467)); call assertmodelok(interval_parameterization( mm => 11.674523)); call assertmodelok(interval_parameterization( dd => 0.235690)); call assertmodelok(interval_parameterization( dd => 700.546798)); call assertmodelok(interval_parameterization( ss => 47243.347200)); call assertmodelok(interval_parameterization( mm => -0.54, dd => 17.4)); call assertmodelok(interval_parameterization( mm => -0.55, dd => 17.4)); call assertmodelok(interval_parameterization( mm => 0.11, dd => -1)); call assertmodelok(interval_parameterization( mm => 0.12, dd => -1)); call assertmodelok(interval_parameterization( dd => 1.2)); call assertmodelok(interval_parameterization( dd => 0.9)); call assertmodelok(interval_parameterization( dd => 1, hh => -2, mi => 24, ss => 0)); call assertmodelok(interval_parameterization( dd => 0, hh => 21, mi => 36, ss => 0)); call assertmodelok(interval_parameterization( yy => 19, mm => -1, dd => 17, hh => -100, mi => 87, ss => -76)); call assertmodelok(interval_parameterization( yy => 9.7, mm => -1.55, dd => 17.4, hh => -99.7, mi => 86.7, ss => -75.7)); call assertmodelok(interval_parameterization( yy => -9.7, mm => 1.55, dd => -17.4, hh => 99.7, mi => -86.7, ss => 75.7)); end; $body$; ``` Invoke it like this: ```plpgsql call testinternalintervalrepresentationmodel(); ``` Each call of assertmodelok() finishes silently showing that the hypothesized rules have so far been shown always to agree with the rules of the actual implementation. You are challenged to disprove the hypothesis by inventing more tests. If any of your tests causes assertmodelok() to finish with an assert failure, then against this documentation section. If you do find such a counter-example, you can compare the results from using the actual implementation and the modeled implementation with this re-write of the logic of the assertmodelok()

Instead of using assert, it shows you the outputs for visual comparison: ```plgsql drop function if exists modelvsactualcomparison(intervalparameterization_t) cascade; create function modelvsactualcomparison(p in intervalparameterization_t) returns table(x text) language plpgsql as $body$ declare imodeled constant intervalmmddsst not null := intervalmmddss(p); iactual constant interval not null := intervalvalue(p); pmodeled constant intervalparameterizationt not null := parameterization(imodeled); pactual constant intervalparameterizationt not null := parameterization(iactual); ssmodeledtext constant text not null := ltrim(tochar(pmodeled.ss, '9999999999990.999999')); ssactualtext constant text not null := ltrim(tochar(pactual.ss, '9999999999990.999999')); begin x := 'modeled: '|| lpad(p_modeled.yy ::text, 4)||' yy, '|| lpad(p_modeled.mm ::text, 4)||' mm, '|| lpad(p_modeled.dd ::text, 4)||' dd, '|| lpad(p_modeled.hh ::text, 4)||' hh, '|| lpad(p_modeled.mi ::text, 4)||' mi, '|| lpad(ssmodeledtext, 10)||' ss'; return next; x := 'actual: '|| lpad(p_actual.yy ::text, 4)||' yy, '|| lpad(p_actual.mm ::text, 4)||' mm, '|| lpad(p_actual.dd ::text, 4)||' dd, '|| lpad(p_actual.hh ::text, 4)||' hh, '|| lpad(p_actual.mi ::text, 4)||' mi, '|| lpad(ssactualtext, 10)||' ss'; return next; end; $body$; ``` Use it like this: ```plpgsql select x from modelvsactualcomparison(intervalparameterization( yy => -9.7, mm => 1.55, dd => -17.4, hh => 99.7, mi => -86.7, ss => 75.7)); ``` This is the result: ```output modeled: -9 yy, -7 mm, -1 dd, 100 hh, 40 mi, 33.700000 ss actual: -9 yy, -7 mm, -1 dd, 100 hh, 40 mi, 33.700000 ss ``` The makeinterval() built-in procedure has int input formal parameters for years, months, days, hours, and minutes; and it has a double precision input formal parameter for seconds. It also, as a bonus, has an int input formal parameter for weeks (where this is treated simply as 7 days). This parameterization suggests that the PostgreSQL designers saw no use-case for supplying the quantities that use int input formal parameters as real numbers and that it's an unintended consequence of the ::interval typecast approach to construct an interval_ value that it allows a parameterization that provides real numbers for these values. Nevertheless, the implementation supports this and therefore must be documented, as the page has done. You can substitute the following alternative implementation of the intervalvalue()_ function for the implementation given above: ```plpgsql drop function if exists intervalvalue(intervalparameterization_t) cascade; create function intervalvalue(p in intervalparameterization_t) returns interval language plpgsql as $body$ declare yy int not null := p.yy::int; mm int not null := p.mm::int; dd int not null := p.dd::int; hh int not null := p.hh::int; mi int not null := p.mi::int; begin return make_interval( years => yy, months => mm, days => dd, hours => hh, mins => mi, secs => p.ss); end; $body$; ``` Of course, to test it you must supply only integral values for years, months, days, hours, and minutes like this: ```plpgsql call assertmodelok(interval_parameterization()); call assertmodelok(interval_parameterization( mm => 99, dd => 700, ss => 83987851.522816 )); call assertmodelok(interval_parameterization( yy => 3, mm => 8)); call assertmodelok(interval_parameterization( mm => 11, dd => 17)); call assertmodelok(interval_parameterization( hh => 123456)); call assertmodelok(interval_parameterization( dd => 123456)); call assertmodelok(interval_parameterization( ss => 47243.347200)); call assertmodelok(interval_parameterization( mm => -1, dd => 17)); call assertmodelok(interval_parameterization( mm => 11, dd => -1)); call assertmodelok(interval_parameterization( dd => 1, hh => -2, mi => 24, ss => 0)); call assertmodelok(interval_parameterization( dd => 0, hh => 21, mi => 36, ss => 0)); call assertmodelok(interval_parameterization( yy => 19, mm => -1, dd => 17, hh => -100, mi => 87, ss => -76)); ``` Each of the calls to assertmodelok() finishes silently, showing that the assertions that it tests hold. Many more, and more varied, tests than are shown here were run while this example was being developed. The tested assertions have never been seen to fail.