Arithmetic operation resulted in an overflow

Posts   
 
    
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 03-Aug-2011 21:26:19   

3.0 - Oracle ODP - Adapter

Calling GetScalar with an aggregate function of Avg. The database type is Number. In LLBL Designer, it's decimal 38.

The following query is being generated and executed:

Query: SELECT * FROM (SELECT TRUNC(AVG("TS"."DASHBOARD_V"."AGE"), 2sunglasses AS "Age" FROM "TS"."DASHBOARD_V" WHERE ( "TS"."DASHBOARD_V"."ASSIGNED_TO" = stuck_out_tongue_winking_eye 1)) WHERE rownum <= 1

Getting an exception of "Arithmetic operation resulted in an overflow" as soon as we call GetScalar.

If we run the query manually, we get back the correct avg.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 03-Aug-2011 23:52:03   

Please post the full exception message and stack trace. Also we need to know the runtime library version (http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=7722).

What do you mean by "If we run the query manually, we get back the correct avg"?

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Aug-2011 00:30:52   

SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryExecutionException was caught
  Message=An exception was caught during the execution of a retrieval query: Arithmetic operation resulted in an overflow.. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception.
  Source=SD.LLBLGen.Pro.ORMSupportClasses.NET20
  RuntimeBuild=05262010
  RuntimeVersion=3.0.0.0
  QueryExecuted=
    Query: SELECT * FROM (SELECT TRUNC(AVG("TS"."DASHBOARD_V"."AGE"), 28) AS "Age" FROM "TS"."DASHBOARD_V" WHERE ( "TS"."DASHBOARD_V"."ASSIGNED_TO" = :p1)) WHERE rownum <= 1
    Parameter: :p1 : String. Length: 30. Precision: 0. Scale: 0. Direction: Input. Value: "PAPP".

  StackTrace:
       at SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.ExecuteScalar()
       at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.ExecuteScalarQuery(IRetrievalQuery queryToExecute)
       at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetScalar(IEntityFields2 fields, IPredicate filter, IGroupByCollection groupByClause, IRelationCollection relations)
       at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetScalar(IEntityField2 field, IExpression expressionToExecute, AggregateFunction aggregateToApply, IPredicate filter, IGroupByCollection groupByClause, IRelationCollection relations)
       at SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.GetScalar(IEntityField2 field, IExpression expressionToExecute, AggregateFunction aggregateToApply, IPredicate filter)
       at CTS.RemotingImplementationClasses.GeneralFunctions.GetScalar(RemotingSession& session, IEntityField2 entityField, AggregateFunction aggegrateFunction, RelationPredicateBucket predicate) in D:\Development\BOLI\Projects\CTS\Source\BusinessObjects\RemotingImplementationClasses\Utility\GeneralFunctions.cs:line 35
  InnerException: System.OverflowException
       Message=Arithmetic operation resulted in an overflow.
       Source=Oracle.DataAccess
       StackTrace:
            at Oracle.DataAccess.Types.DecimalConv.GetDecimal(IntPtr numCtx)
            at Oracle.DataAccess.Client.OracleDataReader.GetDecimal(Int32 i)
            at Oracle.DataAccess.Client.OracleDataReader.GetValue(Int32 i)
            at Oracle.DataAccess.Client.OracleCommand.ExecuteScalar()
            at SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.ExecuteScalar()

Also, what I meant was that if we run the generated query directly in oracle using toad, it works and returns an avg number with decimal places.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Aug-2011 07:24:20   

If you look closely into the InnerException you will see the errors comes from ODP.Net trying to unboxing the decimal (Oracle.DataAccess.Client.OracleDataReader.GetDecimal(Int32 i)).

What large are your DB values actually? (Do you have over estimate the precision but you don't need it?). From pasts threads, there is no much the framework can do about it. Using TypeConverters is not of much help here as the exception is raised in ODP side. See this for example: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=13264

Possible workarounds: - Look into your data to see if you actually use that large precision. See if you can truncate those values. - See if you can modify your schema to use more realistic precision. - You can override DataAccessAdatper's HandleValueReadErrors method in a partial class and put Oracle specific code to handle this. In that method you get the Exception, the dataReader and the values you should fill into.

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Aug-2011 16:00:29   

The number that oracle is returning is

379.0439858454766241651487553126

So it must be a precision thing. How do I go about getting Oracle to limit the precision?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Aug-2011 21:48:41   

Ok. First of all, the best option is that you fix the schema to a realistic (precision, scale) so the calculations would be shorter.

Second of all, I want to explain some things: As the OracleDecimal type is larger than the .Net System.Decimal (38 vs. 28 precision), you will receive potentially decimal overflows in two places: entity fetches and aggregates. For instance, if you have a value 379.0439858454766241651487553126 in your database, this test will fail:

[TestMethod]
public void ReproduceDecimalOverflowForEntityFetch()
{
    TableaEntity t31 = new TableaEntity(1);
    using (var adapter = new DataAccessAdapter())
    {
        adapter.FetchEntity(t31);       
    }

    // at db: 379.0439858454766241651487553126, we expect an overflow exception
    Assert.AreEqual( 379.0439858454766241651487553126, t31.B);
}

Note that the precision of that number is 31, that is why ODP.Net will raise an overflow, because it can't unbox in the IDataReader.

The other situation is when you fetch an Aggregate (like AVG). As you saw, LLBLGen Framework tries to avoid this emitting a TRUNC(..., 28 ). Nevertheless this doesn't eliminate the potential overflow, because it only trunc the decimal part, but no adjust the precision (I don't know any PL/SQL function that does that). So if the AVG returns 379.0439858454766241651487553126, the TRUNC(...,28 ) will return the same number, however it still has a precision=31. So that will raise an overflow exception from ODP as well. That is what you are experiencing. This is an example of that expected exception:

[TestMethod]
public void ReproduceDecimalOverflowForGetScalar()
{
    decimal avg = decimal.Zero;
    using (var adapter = new DataAccessAdapterOverflowSafe())
    {
        avg = (decimal)adapter.GetScalar(TableaFields.B, AggregateFunction.Avg);
    }

    // test the avg. We already get an exception at this point due to an ODP.Net decimal overflow
    Assert.AreEqual(379.0439858454766241651487553126M, avg);
}

So, now go to the solutions. For the first scenario (entity fetches) you should override the DataAccessAdapter's **HandleValueReadErrors **method. I'm not that expert at ODP.Net but this worked for me:

protected override bool HandleValueReadErrors(IDataReader openReader, object[] toFill, Exception ex)
{
    // get the actual OracleDataReader
    OracleDataReader oraReader = (OracleDataReader)openReader;

    // iterate the values and fillt them
    for (int i = 0; i < toFill.Length; i++)
    {
        string typeAsString = oraReader.GetDataTypeName(i);
        switch (typeAsString)
        {
            case "Decimal":
                // get the decimal avoiding the unboxing overflow
                decimal d = (decimal)(OracleDecimal.SetPrecision(oraReader.GetOracleDecimal(i), 28));
                toFill[i] = d;
                break;

            default:
                toFill[i] = openReader.GetValue(i);
                break;
        }
    }

    // this means that we handled the exception
    return true;
}

For the second scenario (Aggregate fetches) it's different because the error comes directly from a command execute, and not from an IDataReader. That's why the TRUNC is emitted. So, the solution is emit a smaller value for the decimalPlaces parameter of the TRUNC function. For that you have create a custom derived OracleSpecificCreator, DynamicQueryEngine and DataAccessAdatper classes, and override the OracleSpecificCreator's **ConstructCallToAggregateWithFieldAsParameter **method. Similar to this:

protected override string ConstructCallToAggregateWithFieldAsParameter(AggregateFunction function, string fieldName)
    {
        string toReturn = string.Empty;

        // this is the custom part. We will trunc all decimal results to only have 10 decimal places
        int decimalPlaces = 10;

        switch (function)
        {
            case AggregateFunction.Avg:
                toReturn = string.Format("TRUNC(AVG({0}), {1})", fieldName, decimalPlaces);
                break;
            case AggregateFunction.AvgDistinct:
                toReturn = string.Format("TRUNC(AVG(DISTINCT {0}), {1})", fieldName, decimalPlaces);
                break;

            ...
        }
        return toReturn;
    }
}

Sounds difficult? Well, I already have this coded for you. I'm attaching one file to this post. The only you have to do is: 1. Copy it to your DBSpecific project. 2. Change the first namespace of the file to your own DBSpecific one. 3. Add a reference to the Oracle.DataAccess assembly into your DBSpecific project. 4. Now you can use it in your code, it's pretty easy:

[TestMethod]
public void ReproduceDecimalOverflowForEntityFetch()
{
    TableaEntity t31 = new TableaEntity(1);
    using (var adapter = new DataAccessAdapterOverflowSafe())
    {
        adapter.FetchEntity(t31);       
    }

    // at db: 379.0439858454766241651487553126, we expect the truncated value
    Assert.AreEqual(379.0439858454766241651487553M, t31.B);
}

[TestMethod]
public void ReproduceDecimalOverflowForGetScalar()
{
    decimal avg = decimal.Zero;
    using (var adapter = new DataAccessAdapterOverflowSafe())
    {
        avg = (decimal)adapter.GetScalar(TableaFields.B, AggregateFunction.Avg);
    }

    // test the avg. As we trunc at our custom adapter, we expect only 10 decimal digits
    Assert.AreEqual(379.0439858454M, avg);
}

Hope helpful wink

Attachments
Filename File size Added on Approval
OracleSpecificCreatorOverflowSafe.cs 7,010 04-Aug-2011 21:49.07 Approved
David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Aug-2011 23:26:43   

daelmo wrote:

Ok. First of all, the best option is that you fix the schema to a realistic (precision, scale) so the calculations would be shorter.

What do you mean by this? The column in the database is now a number(3). LLBL is generating this in the .net layer as Int16.

But when LLBL generates the query, it's inserting the trunc( ,28 ).

Who/what is turning this into a number with two much precision?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Aug-2011 23:53:01   

happyfirst wrote:

daelmo wrote:

Ok. First of all, the best option is that you fix the schema to a realistic (precision, scale) so the calculations would be shorter.

What do you mean by this? The column in the database is now a number(3). LLBL is generating this in the .net layer as Int16.

I was just guessing coz I don't know what is the precision/scale of your columns. I was just pointing that in case you have that problem as well in entity fetches. So, don't worry about that.

happyfirst wrote:

But when LLBL generates the query, it's inserting the trunc( ,28 ).

Who/what is turning this into a number with two much precision?

The AVG function is the responsible of that number. If you have three ints 7,9,3, applying the AVG will generate 6.33333... That's why LLBLGen is placing trunc(,28 ).

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 04-Aug-2011 23:58:15   

daelmo wrote:

The AVG function is the responsible of that number. If you have three ints 7,9,3, applying the AVG will generate 6.33333... That's why LLBLGen is placing trunc(,28 ).

And where exactly is this failing? LLBL getting out the number or Oracle trying to get the number out to give to LLBL?

So how does somebody get an avg via an aggregrate function? What am I doing differently than others?

Do I need to actually UP the precision of my age so that it's a decimal?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 05-Aug-2011 05:16:29   

happyfirst wrote:

daelmo wrote:

The AVG function is the responsible of that number. If you have three ints 7,9,3, applying the AVG will generate 6.33333... That's why LLBLGen is placing trunc(,28 ).

And where exactly is this failing? LLBL getting out the number or Oracle trying to get the number out to give to LLBL?

The second: ODP.Net trying to load the decimal. If you inspect the InnerException it comes to:

Oracle.DataAccess.Types.DecimalConv.GetDecimal(IntPtr numCtx) Oracle.DataAccess.Client.OracleDataReader.GetDecimal(Int32 i) Oracle.DataAccess.Client.OracleDataReader.GetValue(Int32 i) Oracle.DataAccess.Client.OracleCommand.ExecuteScalar() SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.ExecuteScalar() in C:\Program Files\Solutions Design\LLBLGen Pro v3.1\SourceCode\Frameworks\LLBLGen Pro\RuntimeLibraries\ORMSupportClasses\Query\RetrievalQuery.cs

happyfirst wrote:

So how does somebody get an avg via an aggregrate function? What am I doing differently than others?

This actually happened before: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=17508 http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=14441

In fact, the trunc workaround comes from this thread: http://www.llblgen.com/TinyForum/Messages.aspx?ThreadID=4543

Some people just emit an additional DBFunctionCall to the GetScalar call to generate a custom trunc. some others never came into values so large, some others uses the code I gave you. So this is something we should live with since the new version of ODP.Net can unbox larger decimal values (oracledecimal precision > .net-decimal precision). You can search on the web to see that this is a common issue. People that uses directly ODP.Net (without DBProviderFactories) can place custom Oracle code to overcome this (like the one I posted in my earlier post).

happyfirst wrote:

Do I need to actually UP the precision of my age so that it's a decimal?

No, as I said the problem is that the OracleDecimal precision (38 ) is larger than the .Net decimal (28 ) and when the ODP IDataReader tries to load it, it fails due to the overflow. So the trunc in the sql is indeed needed. Have you tried my code in the above post?

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 05-Aug-2011 16:10:47   

I haven't tried your fixes yet because I'm still trying to grasp what the real issue is. The sql call is being generated already with a trunc ( , 28 ). So where is the 38 number coming from?

One of your fixes let's me customize that trunc, but if the current trunc 28 isn't working, it seems anything I do wouldn't work either.

We return decimals from Oracle all the time with no problems. Is the oracle average command what always returns a 38 number?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 05-Aug-2011 17:18:01   

happyfirst wrote:

I haven't tried your fixes yet because I'm still trying to grasp what the real issue is. The sql call is being generated already with a trunc ( , 28 ). So where is the 38 number coming from?

Answered above:

daelmo wrote:

No, as I said the problem is that the OracleDecimal precision (38 ) is larger than the .Net decimal (28 ) and when the ODP IDataReader tries to load it, it fails due to the overflow. So the trunc in the sql is indeed needed. Have you tried my code in the above post?

happyfirst wrote:

One of your fixes let's me customize that trunc, but if the current trunc 28 isn't working, it seems anything I do wouldn't work either.

Please givie it a try and you will see. It isn't working coz, even if the trunc-28 is emited the number that comes from oracle has a precision of 31 (your result) so there are three digits remaining (overflowing). Do you get the idea? Even if you set the trunc to 15 could be a potential error if the integer part has a length greater than 13 for instance.

happyfirst wrote:

We return decimals from Oracle all the time with no problems. Is the oracle average command what always returns a 38 number?

Yes. As I said before, the problem is not Oracle directly, nor LLBLGen, it's ODP.Net unboxing the OracleDecimal to .Net decimal.

David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 05-Aug-2011 19:38:02   

I'm trying your attached file but I'm getting a compile error

Error 1 'SD.LLBLGen.Pro.DQE.Oracle.OracleSpecificCreatorOverflowSafe. ConstructCallToAggregateWithFieldAsParameter(SD.LLBLGen.Pro.ORMSupportClasses .AggregateFunction, string)': no suitable method found to override D:\Development\Project\BusinessObjects\DatabaseSpecific\DataAccessAdapterOverflowSafe.cs 88 35 TSDBSpecific

Is this code LLBL 3.0 compatible?

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 06-Aug-2011 00:05:10   

My mistake. That is for v3.1 which changed that method actually. Attached is a version for v3.0.

Attachments
Filename File size Added on Approval
OracleSpecificCreatorOverflowSafe.cs 7,470 06-Aug-2011 00:05.23 Approved
David Elizondo | LLBLGen Support Team
happyfirst
User
Posts: 215
Joined: 28-Nov-2008
# Posted on: 08-Aug-2011 17:12:28   

Thanks!!!

That worked.