diff --git a/centrallix-lib/include/datatypes.h b/centrallix-lib/include/datatypes.h index da7b509d8..465c99bb2 100644 --- a/centrallix-lib/include/datatypes.h +++ b/centrallix-lib/include/datatypes.h @@ -56,8 +56,7 @@ typedef struct _SV /** Money structure **/ typedef struct _MN { - int WholePart; /* integer part = floor($amount) */ - unsigned short FractionPart; /* fract part = $amount - floor($amount) */ + long long Value; /* Fixed-point representation in 1/10000 of a dollar */ } MoneyType, *pMoneyType; diff --git a/centrallix/Makefile.in b/centrallix/Makefile.in index 5af29cedb..2bbb74912 100644 --- a/centrallix/Makefile.in +++ b/centrallix/Makefile.in @@ -367,6 +367,8 @@ ifeq ($TOTESTFILES,) else TOTESTDEPS=test_obj endif +CTESTHELPERS:=$(wildcard tests/helpers/*.c) +TESTHELPERS:=$(patsubst %.c,%.o,$(CTESTHELPERS)) # Some basic build parameters. PROFILE=@PROFILE@ @@ -529,8 +531,8 @@ tests/centrallix.conf-test: tests/centrallix.conf-test.in tests/t_driver.o: tests/t_driver.c $(CC) $(CFLAGS) $< -c -o $@ -tests/test_%.bin: tests/test_%.o tests/t_driver.o centrallix.o $(V3LSOBJS) $(STATIC_LIBS) - $(CC) $< tests/t_driver.o centrallix.o $(V3LSOBJS) $(LIBDIRS) $(PROFILE) $(COVERAGE) -Wl@EXPORT_DYNAMIC@ -o $@ $(LIBS) @NCURSES_LIBS@ +tests/test_%.bin: tests/test_%.o tests/t_driver.o centrallix.o $(V3LSOBJS) $(STATIC_LIBS) $(TESTHELPERS) + $(CC) $< tests/t_driver.o centrallix.o $(V3LSOBJS) $(TESTHELPERS) $(LIBDIRS) $(PROFILE) $(COVERAGE) -Wl@EXPORT_DYNAMIC@ -o $@ $(LIBS) @NCURSES_LIBS@ @SYBASE_LIBS@ test: tests/centrallix.conf-test $(TOTESTDEPS) $(TOTESTFILES) $(CBTESTFILES) @printf "%-62.62s %s\n" "Test Name" "Stat" @@ -646,6 +648,7 @@ clean: @rm -f man/*.gz @rm -f tests/*.to.tmp tests/*.out @rm -f tests/*.o tests/*.bin + @rm -f tests/helpers/*.o @rm -rf thirdparty/*-inst @rm -f *.gcov */*.gcov @rm -f *.gcda */*.gcda diff --git a/centrallix/expression/exp_compiler.c b/centrallix/expression/exp_compiler.c index 613c87c3f..28a6f48f2 100644 --- a/centrallix/expression/exp_compiler.c +++ b/centrallix/expression/exp_compiler.c @@ -152,8 +152,7 @@ exp_internal_CompileExpression_r(pLxSession lxs, int level, pParamObjects objlis if (t == MLX_TOK_INTEGER) { i = mlxIntVal(lxs); - etmp->Types.Money.WholePart = i; - etmp->Types.Money.FractionPart = 0; + etmp->Types.Money.Value = (i * 10000ll); } else { diff --git a/centrallix/expression/exp_evaluate.c b/centrallix/expression/exp_evaluate.c index 7fc29ae0f..5514c1c07 100644 --- a/centrallix/expression/exp_evaluate.c +++ b/centrallix/expression/exp_evaluate.c @@ -191,7 +191,6 @@ expEvalDivide(pExpression tree, pParamObjects objlist) pExpression i0,i1; MoneyType m; int i; - int is_negative = 0; long long mv, mv2; double md; @@ -227,7 +226,7 @@ expEvalDivide(pExpression tree, pParamObjects objlist) /** Check for divide by zero **/ if ((i1->DataType == DATA_T_INTEGER && i1->Integer == 0) || (i1->DataType == DATA_T_DOUBLE && i1->Types.Double == 0.0) || - (i1->DataType == DATA_T_MONEY && i1->Types.Money.WholePart == 0 && i1->Types.Money.FractionPart == 0)) + (i1->DataType == DATA_T_MONEY && i1->Types.Money.Value == 0)) { mssError(1,"EXP","Attempted divide by zero"); return -1; @@ -280,38 +279,12 @@ expEvalDivide(pExpression tree, pParamObjects objlist) case DATA_T_INTEGER: tree->DataType = DATA_T_MONEY; memcpy(&m, &(i0->Types.Money), sizeof(MoneyType)); - if (m.WholePart < 0) - { - is_negative = !is_negative; - if (m.FractionPart != 0) - { - m.WholePart = m.WholePart + 1; - m.FractionPart = 10000 - m.FractionPart; - } - m.WholePart = -m.WholePart; - } - i = i1->Integer; - if (i < 0) - { - i = -i; - is_negative = !is_negative; - } - if (i == 0) + if (i1->Integer == 0) { mssError(1,"EXP","Attempted divide by zero"); return -1; } - tree->Types.Money.WholePart = m.WholePart / i; - tree->Types.Money.FractionPart = (10000*(m.WholePart % i) + m.FractionPart)/i; - if (is_negative) - { - if (tree->Types.Money.FractionPart != 0) - { - tree->Types.Money.WholePart = tree->Types.Money.WholePart + 1; - tree->Types.Money.FractionPart = 10000 - tree->Types.Money.FractionPart; - } - tree->Types.Money.WholePart = -tree->Types.Money.WholePart; - } + tree->Types.Money.Value = (long long)(m.Value / i1->Integer); break; case DATA_T_DOUBLE: tree->DataType = DATA_T_MONEY; @@ -320,23 +293,11 @@ expEvalDivide(pExpression tree, pParamObjects objlist) mssError(1,"EXP","Attempted divide by zero"); return -1; } - mv = ((long long)(i0->Types.Money.WholePart)) * 10000 + i0->Types.Money.FractionPart; - md = mv / i1->Types.Double; - if (md < 0) md -= 0.5; - else md += 0.5; - mv = md; - tree->Types.Money.WholePart = mv/10000; - mv = mv % 10000; - if (mv < 0) - { - mv += 10000; - tree->Types.Money.WholePart -= 1; - } - tree->Types.Money.FractionPart = mv; + tree->Types.Money.Value = llround(i0->Types.Money.Value/i1->Types.Double); break; case DATA_T_MONEY: - mv = ((long long)(i0->Types.Money.WholePart)) * 10000 + i0->Types.Money.FractionPart; - mv2 = ((long long)(i1->Types.Money.WholePart)) * 10000 + i1->Types.Money.FractionPart; + mv = i0->Types.Money.Value; + mv2 = i1->Types.Money.Value; if (mv2 == 0) { mssError(1,"EXP","Attempted divide by zero"); @@ -428,8 +389,7 @@ expEvalMultiply(pExpression tree, pParamObjects objlist) break; case DATA_T_MONEY: tree->DataType = DATA_T_MONEY; - mv = ((long long)(i1->Types.Money.WholePart)) * 10000 + i1->Types.Money.FractionPart; - mv *= i0->Integer; + mv = i1->Types.Money.Value * i0->Integer; break; case DATA_T_STRING: tree->DataType = DATA_T_STRING; @@ -453,8 +413,7 @@ expEvalMultiply(pExpression tree, pParamObjects objlist) { case DATA_T_MONEY: tree->DataType = DATA_T_MONEY; - mv = ((long long)(i1->Types.Money.WholePart)) * 10000 + i1->Types.Money.FractionPart; - mv *= i0->Types.Double; + mv = i1->Types.Money.Value * i0->Types.Double; break; default: tree->Types.Double = i0->Types.Double * objDataToDouble(i1->DataType, dptr); @@ -465,7 +424,7 @@ expEvalMultiply(pExpression tree, pParamObjects objlist) case DATA_T_MONEY: tree->DataType = DATA_T_MONEY; - mv = ((long long)(i0->Types.Money.WholePart)) * 10000 + i0->Types.Money.FractionPart; + mv = i0->Types.Money.Value; switch(i1->DataType) { case DATA_T_INTEGER: @@ -505,14 +464,7 @@ expEvalMultiply(pExpression tree, pParamObjects objlist) /** Common processing **/ if (tree->DataType == DATA_T_MONEY) { - tree->Types.Money.WholePart = mv/10000; - mv = mv % 10000; - if (mv < 0) - { - mv += 10000; - tree->Types.Money.WholePart -= 1; - } - tree->Types.Money.FractionPart = mv; + tree->Types.Money.Value = mv; } else if (tree->DataType == DATA_T_STRING) { @@ -604,17 +556,9 @@ expEvalMinus(pExpression tree, pParamObjects objlist) tree->Types.Double = (double)(i0->Integer) - i1->Types.Double; break; case DATA_T_MONEY: + /** Treat Int as a dollar value **/ tree->DataType = DATA_T_MONEY; - tree->Types.Money.WholePart = i0->Integer - i1->Types.Money.WholePart; - if (i1->Types.Money.FractionPart == 0) - { - tree->Types.Money.FractionPart = 0; - } - else - { - tree->Types.Money.WholePart--; - tree->Types.Money.FractionPart = 10000 - i1->Types.Money.FractionPart; - } + tree->Types.Money.Value = (i0->Integer * 10000ll) - i1->Types.Money.Value; break; default: tree->Integer = i0->Integer - objDataToInteger(i1->DataType, dptr, NULL); @@ -635,7 +579,7 @@ expEvalMinus(pExpression tree, pParamObjects objlist) break; case DATA_T_MONEY: tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = i0->Types.Double - (i1->Types.Money.WholePart + i1->Types.Money.FractionPart/10000.0); + tree->Types.Double = i0->Types.Double - (i1->Types.Money.Value / 10000.0); break; default: tree->DataType = DATA_T_DOUBLE; @@ -649,25 +593,16 @@ expEvalMinus(pExpression tree, pParamObjects objlist) { case DATA_T_INTEGER: tree->DataType = DATA_T_MONEY; - tree->Types.Money.WholePart = i0->Types.Money.WholePart - i1->Integer; - tree->Types.Money.FractionPart = i0->Types.Money.FractionPart; + /** Since Value is in 1/10000 of a dollar, integer must be multiplied to scale **/ + tree->Types.Money.Value = i0->Types.Money.Value - (i1->Integer * 10000ll); break; case DATA_T_DOUBLE: tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = (i0->Types.Money.WholePart + i0->Types.Money.FractionPart/10000.0) - i1->Types.Double; + tree->Types.Double = (i0->Types.Money.Value / 10000.0) - i1->Types.Double; break; case DATA_T_MONEY: tree->DataType = DATA_T_MONEY; - tree->Types.Money.WholePart = i0->Types.Money.WholePart - i1->Types.Money.WholePart; - tree->Types.Money.FractionPart = 10000 + i0->Types.Money.FractionPart - i1->Types.Money.FractionPart; - if (tree->Types.Money.FractionPart >= 10000) - { - tree->Types.Money.FractionPart -= 10000; - } - else - { - tree->Types.Money.WholePart--; - } + tree->Types.Money.Value = i0->Types.Money.Value - i1->Types.Money.Value; break; default: if (objDataToMoney(i1->DataType, dptr, &m) < 0) @@ -676,12 +611,7 @@ expEvalMinus(pExpression tree, pParamObjects objlist) return -1; } tree->DataType = DATA_T_MONEY; - tree->Types.Money.WholePart = i0->Types.Money.WholePart - m.WholePart; - tree->Types.Money.FractionPart = 10000 + i0->Types.Money.FractionPart - m.FractionPart; - if (tree->Types.Money.FractionPart >= 10000) - tree->Types.Money.FractionPart -= 10000; - else - tree->Types.Money.WholePart--; + tree->Types.Money.Value = i0->Types.Money.Value - m.Value; break; } break; @@ -806,8 +736,8 @@ expEvalPlus(pExpression tree, pParamObjects objlist) case DATA_T_MONEY: tree->DataType = DATA_T_MONEY; - tree->Types.Money.WholePart = i1->Types.Money.WholePart + i0->Integer; - tree->Types.Money.FractionPart = i1->Types.Money.FractionPart; + /** Int must be converted to 1/10000 of a dollar **/ + tree->Types.Money.Value = i1->Types.Money.Value + (i0->Integer * 10000ll); break; default: @@ -843,13 +773,7 @@ expEvalPlus(pExpression tree, pParamObjects objlist) case DATA_T_MONEY: objDataToMoney(i1->DataType, dptr, &m); - tree->Types.Money.WholePart = i0->Types.Money.WholePart + m.WholePart; - tree->Types.Money.FractionPart = i0->Types.Money.FractionPart + m.FractionPart; - if (tree->Types.Money.FractionPart >= 10000) - { - tree->Types.Money.FractionPart -= 10000; - tree->Types.Money.WholePart++; - } + tree->Types.Money.Value = i0->Types.Money.Value + m.Value; break; case DATA_T_DATETIME: @@ -1502,8 +1426,7 @@ expRevEvalProperty(pExpression tree, pParamObjects objlist) } else if (tree->DataType == DATA_T_INTEGER && attr_type == DATA_T_MONEY) { - tree->Types.Money.WholePart = tree->Integer; - tree->Types.Money.FractionPart = 0; + tree->Types.Money.Value = (tree->Integer * 10000ll); } else if (tree->DataType == DATA_T_DOUBLE && attr_type == DATA_T_MONEY) { diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index 265953b03..27d22037e 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -266,24 +266,11 @@ int exp_fn_abs(pExpression tree, pParamObjects objlist, pExpression i0, pExpress break; case DATA_T_MONEY: - if (i0->Types.Money.WholePart >= 0) - { - tree->Types.Money.WholePart = i0->Types.Money.WholePart; - tree->Types.Money.FractionPart = i0->Types.Money.FractionPart; - } - else - { - if (i0->Types.Money.FractionPart != 0) - { - tree->Types.Money.WholePart = -(i0->Types.Money.WholePart + 1); - tree->Types.Money.FractionPart = 10000 - i0->Types.Money.FractionPart; - } + if (i0->Types.Money.Value >= 0) + tree->Types.Money.Value = i0->Types.Money.Value; else - { - tree->Types.Money.WholePart = -i0->Types.Money.WholePart; - tree->Types.Money.FractionPart = 0; - } - } + tree->Types.Money.Value = -i0->Types.Money.Value; + break; default: @@ -1663,7 +1650,7 @@ int exp_fn_round(pExpression tree, pParamObjects objlist, pExpression i0, pExpre break; case DATA_T_MONEY: - mt = ((long long)(i0->Types.Money.WholePart)) * 10000 + i0->Types.Money.FractionPart; + mt = i0->Types.Money.Value; if (dec < 4) { mv = 1; @@ -1675,14 +1662,7 @@ int exp_fn_round(pExpression tree, pParamObjects objlist, pExpression i0, pExpre mt /= mv; mt *= mv; } - tree->Types.Money.WholePart = mt/10000; - mt = mt % 10000; - if (mt < 0) - { - mt += 10000; - tree->Types.Money.WholePart -= 1; - } - tree->Types.Money.FractionPart = mt; + tree->Types.Money.Value = mt; break; } return 0; @@ -1936,7 +1916,7 @@ int exp_fn_truncate(pExpression tree, pParamObjects objlist, pExpression i0, pEx break; case DATA_T_MONEY: - mt = ((long long)(i0->Types.Money.WholePart)) * 10000 + i0->Types.Money.FractionPart; + mt = i0->Types.Money.Value; if (dec < 4) { mv = 1; @@ -1944,14 +1924,7 @@ int exp_fn_truncate(pExpression tree, pParamObjects objlist, pExpression i0, pEx mt /= mv; mt *= mv; } - tree->Types.Money.WholePart = mt/10000; - mt = mt % 10000; - if (mt < 0) - { - mt += 10000; - tree->Types.Money.WholePart -= 1; - } - tree->Types.Money.FractionPart = mt; + tree->Types.Money.Value = mt; break; } return 0; @@ -2947,8 +2920,7 @@ int exp_fn_avg(pExpression tree, pParamObjects objlist, pExpression i0, pExpress sumexp->String[0] = '\0'; sumexp->Integer = 0; sumexp->Types.Double = 0; - sumexp->Types.Money.FractionPart = 0; - sumexp->Types.Money.WholePart = 0; + sumexp->Types.Money.Value = 0; cntexp->Integer = 0; } @@ -3035,8 +3007,7 @@ int exp_fn_sum(pExpression tree, pParamObjects objlist, pExpression i0, pExpress tree->AggExp->String[0] = '\0'; tree->AggExp->Integer = 0; tree->AggExp->Types.Double = 0; - tree->AggExp->Types.Money.FractionPart = 0; - tree->AggExp->Types.Money.WholePart = 0; + tree->AggExp->Types.Money.Value = 0; } expCopyValue(tree->AggExp, (pExpression)(tree->AggExp->Children.Items[0]), 1); expCopyValue(i0, (pExpression)(tree->AggExp->Children.Items[1]), 0); @@ -3098,8 +3069,7 @@ int exp_fn_max(pExpression tree, pParamObjects objlist, pExpression i0, pExpress tree->String[0] = '\0'; tree->Integer = 0; tree->Types.Double = 0; - tree->Types.Money.FractionPart = 0; - tree->Types.Money.WholePart = 0; + tree->Types.Money.Value = 0; expCopyValue(i0,tree,0); } subexp = ((pExpression)(tree->AggExp->Children.Items[2])); @@ -3160,8 +3130,7 @@ int exp_fn_min(pExpression tree, pParamObjects objlist, pExpression i0, pExpress tree->Alloc = 0; tree->Integer = 0; tree->Types.Double = 0; - tree->Types.Money.FractionPart = 0; - tree->Types.Money.WholePart = 0; + tree->Types.Money.Value = 0; expCopyValue(i0,tree,0); } subexp = ((pExpression)(tree->AggExp->Children.Items[2])); diff --git a/centrallix/expression/exp_main.c b/centrallix/expression/exp_main.c index 05225724b..7cc8c31dc 100644 --- a/centrallix/expression/exp_main.c +++ b/centrallix/expression/exp_main.c @@ -523,7 +523,7 @@ exp_internal_DumpExpression_r(pExpression this, int level) case EXPR_N_INTEGER: printf("INTEGER = %d", this->Integer); break; case EXPR_N_STRING: printf("STRING = <%s>", this->String); break; case EXPR_N_DOUBLE: printf("DOUBLE = %.2f", this->Types.Double); break; - case EXPR_N_MONEY: printf("MONEY = %d.%4.4d", this->Types.Money.WholePart, this->Types.Money.FractionPart); break; + case EXPR_N_MONEY: printf("MONEY = %4.4lld", this->Types.Money.Value); break; case EXPR_N_DATETIME: printf("DATETIME = %s", objDataToStringTmp(DATA_T_DATETIME,&(this->Types.Date), 0)); break; case EXPR_N_PLUS: printf("PLUS"); break; case EXPR_N_MULTIPLY: printf("MULTIPLY"); break; @@ -863,7 +863,7 @@ expCompareExpressionValues(pExpression exp1, pExpression exp2) return 0; if (!(exp1->Flags & EXPR_F_NULL) && exp1->DataType == DATA_T_DOUBLE && exp1->Types.Double != exp2->Types.Double) return 0; - if (!(exp1->Flags & EXPR_F_NULL) && exp1->DataType == DATA_T_MONEY && (exp1->Types.Money.WholePart != exp2->Types.Money.WholePart || exp1->Types.Money.FractionPart != exp2->Types.Money.FractionPart)) + if (!(exp1->Flags & EXPR_F_NULL) && exp1->DataType == DATA_T_MONEY && exp1->Types.Money.Value != exp2->Types.Money.Value) return 0; if (!(exp1->Flags & EXPR_F_NULL) && exp1->DataType == DATA_T_DATETIME && (exp1->Types.Date.Part.Second != exp2->Types.Date.Part.Second || exp1->Types.Date.Part.Minute != exp2->Types.Date.Part.Minute || exp1->Types.Date.Part.Hour != exp2->Types.Date.Part.Hour || exp1->Types.Date.Part.Day != exp2->Types.Date.Part.Day || exp1->Types.Date.Part.Month != exp2->Types.Date.Part.Month || exp1->Types.Date.Part.Year != exp2->Types.Date.Part.Year)) return 0; diff --git a/centrallix/expression/exp_params.c b/centrallix/expression/exp_params.c index fc0c4f984..260febff2 100644 --- a/centrallix/expression/exp_params.c +++ b/centrallix/expression/exp_params.c @@ -602,14 +602,12 @@ exp_internal_ResetAggregates(pExpression this, int reset_id, int level) { this->AggExp->Integer = 0; this->AggExp->Types.Double = 0; - this->AggExp->Types.Money.WholePart = 0; - this->AggExp->Types.Money.FractionPart = 0; + this->AggExp->Types.Money.Value = 0; this->Flags |= EXPR_F_AGGLOCKED; } this->Integer = 0; this->Types.Double = 0; - this->Types.Money.WholePart = 0; - this->Types.Money.FractionPart = 0; + this->Types.Money.Value = 0; if (!strcmp(this->Name,"count")) { this->Flags &= ~EXPR_F_NULL; diff --git a/centrallix/include/testhelpers/mssErrorHelper.h b/centrallix/include/testhelpers/mssErrorHelper.h new file mode 100644 index 000000000..54a93c8f1 --- /dev/null +++ b/centrallix/include/testhelpers/mssErrorHelper.h @@ -0,0 +1,13 @@ +#ifndef _MSS_ERROR_HELPER_H +#define _MSS_ERROR_HELPER_H + +/** Authenticates with Centrallix so errors are captured */ +int mssErrorHelper_init(); + +/** + * Returns 1 if the most recent call to mssError contains the string in "message", 0 otherwise. + * You must call mssErrorHelper_init() first so calls to mssError are captured. + */ +int mssErrorHelper_mostRecentErrorContains(char *message); + +#endif /* not defined _MSS_ERROR_HELPER_H */ \ No newline at end of file diff --git a/centrallix/include/testhelpers/stdoutHelper.h b/centrallix/include/testhelpers/stdoutHelper.h new file mode 100644 index 000000000..15a9812bd --- /dev/null +++ b/centrallix/include/testhelpers/stdoutHelper.h @@ -0,0 +1,10 @@ +#ifndef _STDOUT_HELPER_H +#define _STDOUT_HELPER_H + +/** Begin redirecting and capturing stdout output. It will no longer output to the terminal (or wherever else you have it going) */ +int stdoutHelper_startCapture(); + +/** Stop redirecting stdout and return the redirected contents that were captured. You must call stdoutHelper_startCapture() first */ +char* stdoutHelper_stopCaptureAndGetContents(); + +#endif /* not defined _STDOUT_HELPER_H */ \ No newline at end of file diff --git a/centrallix/netdrivers/net_http_rest.c b/centrallix/netdrivers/net_http_rest.c index c236c4da2..418a1da4e 100644 --- a/centrallix/netdrivers/net_http_rest.c +++ b/centrallix/netdrivers/net_http_rest.c @@ -76,8 +76,10 @@ nht_i_RestWriteAttrValue(pNhtConn conn, pObject obj, char* attrname, int data_ty { ObjData od; int rval; + long long whole; + unsigned short fraction; - /** Get the data value **/ + /** Get the data value **/ rval = objGetAttrValue(obj, attrname, data_type, &od); if (rval != 0) { @@ -113,12 +115,14 @@ nht_i_RestWriteAttrValue(pNhtConn conn, pObject obj, char* attrname, int data_ty ); break; - case DATA_T_MONEY: - nht_i_QPrintfConn(conn, 0, "{ \"wholepart\":%INT, \"fractionpart\":%INT }", - od.Money->WholePart, - od.Money->FractionPart - ); - break; + case DATA_T_MONEY: + /** For the time being, 64-bit representation will mimic whole and fraction part handling of negatives + ** with regards to JSON data. It will use the new %LL specifier, but the JSON returned will + ** contain an inverted fraction part that always counts in the positive direction. + **/ + objGetOldRepresentationOfMoney(*(od.Money), &whole, &fraction); + nht_i_QPrintfConn(conn, 0, "{ \"wholepart\":%LL, \"fractionpart\":%INT }", whole, fraction); + break; default: /** Unknown or unimplemented data type **/ diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index 6de7b1f31..ddd310222 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -425,15 +426,15 @@ obj_internal_FormatMoney(pMoneyType m, char* str, char* format, int length) char* fmt; char* ptr; int n_whole_digits = 0; - int print_whole; + long long print_whole; int print_fract; int in_decimal_part = 0; int suppressing_zeros = 1; int automatic_sign = 1; int tens_multiplier = 1; - int d; + long long d; char* start_fmt; - int orig_print_whole; + long long orig_print_whole; char tmp[20]; XString xs; /*int intl_format = 0;*/ @@ -475,7 +476,7 @@ obj_internal_FormatMoney(pMoneyType m, char* str, char* format, int length) if (tens_multiplier > 0) tens_multiplier /= 10; /** Special handling of zeros **/ - if (m->WholePart == 0 && m->FractionPart == 0 && zero_type != 0) + if (m->Value == 0 && zero_type != 0) { if (strlen(zero_strings[zero_type]) >= length) return -1; @@ -490,18 +491,17 @@ obj_internal_FormatMoney(pMoneyType m, char* str, char* format, int length) if (strpbrk(fmt,"+-()[]")) automatic_sign = 0; /** Determine the 'print' version of whole/fraction parts **/ - if (m->WholePart >= 0 || m->FractionPart == 0) - { - print_whole = m->WholePart; - print_fract = m->FractionPart; - } + orig_print_whole = m->Value/10000; + if (m->Value < 0) + { + print_whole = -m->Value/10000; + print_fract = -m->Value%10000; + } else - { - print_whole = m->WholePart + 1; - print_fract = 10000 - m->FractionPart; - } - orig_print_whole = m->WholePart; - if (print_whole < 0) print_whole = -print_whole; + { + print_whole = m->Value/10000; + print_fract = m->Value%10000; + } /** Ok, start generating the thing. **/ while(*fmt) @@ -509,7 +509,7 @@ obj_internal_FormatMoney(pMoneyType m, char* str, char* format, int length) if (automatic_sign) { automatic_sign = 0; - if (orig_print_whole < 0) + if (m->Value < 0) *(str++) = '-'; /*else *(str++) = ' ';*/ @@ -544,7 +544,7 @@ obj_internal_FormatMoney(pMoneyType m, char* str, char* format, int length) } else { - sprintf(tmp,"%d",d); + sprintf(tmp,"%lld",d); xsConcatenate(&xs,tmp,-1); } break; @@ -567,7 +567,7 @@ obj_internal_FormatMoney(pMoneyType m, char* str, char* format, int length) case '.': if (print_whole != 0) { - sprintf(tmp,"%d",print_whole); + sprintf(tmp,"%lld",print_whole); xsConcatenate(&xs,tmp,1); } in_decimal_part = 1; @@ -802,10 +802,10 @@ objDataToInteger(int data_type, void* data_ptr, char* format) case DATA_T_MONEY: m = (pMoneyType)data_ptr; - if (m->FractionPart==0 || m->WholePart>=0) - v = m->WholePart; - else - v = m->WholePart + 1; + if (m->Value/10000 > INT_MAX || m->Value/10000 < INT_MIN) + mssError(1, "OBJ", "Warning: %d (MoneyType) overflow; cannot fit value of that size in int", data_type); + else + v = m->Value/10000; break; case DATA_T_INTVEC: @@ -840,7 +840,7 @@ objDataToDouble(int data_type, void* data_ptr) { case DATA_T_INTEGER: v = *(int*)data_ptr; break; case DATA_T_STRING: v = strtod((char*)data_ptr, NULL); break; - case DATA_T_MONEY: m = (pMoneyType)data_ptr; v = m->WholePart + (m->FractionPart/10000.0); break; + case DATA_T_MONEY: m = (pMoneyType)data_ptr; v = m->Value/10000.0; break; case DATA_T_DOUBLE: v = *(double*)data_ptr; break; default: v = 0.0; break; } @@ -1442,17 +1442,19 @@ objDataToMoney(int data_type, void* data_ptr, pMoneyType m) ptr++; } intval = 0; - m->FractionPart = 0; - m->WholePart = 0; + m->Value = 0; intval = strtoul(ptr, &endptr, 10); - if (intval > 0x7FFFFFFFUL) + /** Checking for overflow, Max UL can be greater than Max LL **/ + if (intval > 0x7FFFFFFFFFFFFFFFLL) return -1; if ((endptr - ptr) != strspn(ptr, "0123456789")) return -1; if (is_neg) - m->WholePart = -intval; + m->Value = -intval*10000ll; else - m->WholePart = intval; + m->Value = intval*10000ll; + + /** Handling the "fraction" portion after the decimal point **/ if (*endptr == (intl_format?',':'.')) { intval = strtoul(endptr+1, &endptr2, 10); @@ -1461,7 +1463,14 @@ objDataToMoney(int data_type, void* data_ptr, pMoneyType m) return -1; while(scale < 4) { scale++; intval *= 10; } while(scale > 4) { scale--; intval /= 10; } - m->FractionPart = intval; + if (is_neg) + { + m->Value -= intval; + } + else + { + m->Value += intval; + } endptr = endptr2; } if (endptr == ptr) @@ -1470,30 +1479,23 @@ objDataToMoney(int data_type, void* data_ptr, pMoneyType m) } if (*endptr == '-') { - m->WholePart = -m->WholePart; + m->Value = -m->Value; is_neg = !is_neg; } - if (is_neg && m->FractionPart != 0) - { - m->WholePart--; - m->FractionPart = 10000 - m->FractionPart; - } break; case DATA_T_DOUBLE: - dbl = *(double*)data_ptr + 0.000001; - m->WholePart = floor(dbl); - m->FractionPart = (dbl - m->WholePart)*10000; + dbl = *(double*)data_ptr * 10000.0; + dbl = round(dbl); + m->Value = (long long)dbl; break; case DATA_T_INTEGER: - m->WholePart = *(int*)data_ptr; - m->FractionPart = 0; + m->Value = *(int*)data_ptr * 10000ll; break; case DATA_T_MONEY: - m->WholePart = ((pMoneyType)data_ptr)->WholePart; - m->FractionPart = ((pMoneyType)data_ptr)->FractionPart; + m->Value = ((pMoneyType)data_ptr)->Value; break; } @@ -1574,9 +1576,9 @@ objDataCompare(int data_type_1, void* data_ptr_1, int data_type_2, void* data_pt case DATA_T_MONEY: m = (pMoneyType)data_ptr_2; - if (m->WholePart > intval) cmp_value = -1; - else if (m->WholePart < intval) cmp_value = 1; - else cmp_value = m->FractionPart?-1:0; + if (m->Value > intval*10000ll) cmp_value = -1; + else if (m->Value < intval*10000ll) cmp_value = 1; + else cmp_value = 0; break; case DATA_T_INTVEC: @@ -1617,9 +1619,9 @@ objDataCompare(int data_type_1, void* data_ptr_1, int data_type_2, void* data_pt case DATA_T_MONEY: objDataToMoney(DATA_T_STRING, data_ptr_1, &m_v); m = (pMoneyType)data_ptr_2; - if (m_v.WholePart > m->WholePart) cmp_value = 1; - else if (m_v.WholePart < m->WholePart) cmp_value = -1; - else cmp_value = m_v.FractionPart - m->FractionPart; + if (m_v.Value > m->Value) cmp_value = 1; + else if (m_v.Value < m->Value) cmp_value = -1; + else cmp_value = 0; break; case DATA_T_DOUBLE: @@ -1673,7 +1675,7 @@ objDataCompare(int data_type_1, void* data_ptr_1, int data_type_2, void* data_pt case DATA_T_MONEY: m = (pMoneyType)data_ptr_2; - dblval = m->WholePart + (m->FractionPart/10000.0); + dblval = m->Value/10000.0; if (dblval == *(double*)data_ptr_1) cmp_value = 0; else if (dblval > *(double*)data_ptr_1) cmp_value = -1; else cmp_value = 1; @@ -1769,9 +1771,9 @@ objDataCompare(int data_type_1, void* data_ptr_1, int data_type_2, void* data_pt } else { - if (m->WholePart > iv->Integers[0]) cmp_value = -1; - else if (m->WholePart < iv->Integers[0]) cmp_value = 1; - else cmp_value = iv->Integers[1] - m->FractionPart; + if (m->Value/10000 > iv->Integers[0]) cmp_value = -1; + else if (m->Value/10000 < iv->Integers[0])cmp_value = 1; + else cmp_value = iv->Integers[1] - m->Value%10000; } break; @@ -1821,9 +1823,9 @@ objDataCompare(int data_type_1, void* data_ptr_1, int data_type_2, void* data_pt switch(data_type_2) { case DATA_T_MONEY: - if (m->WholePart > ((pMoneyType)data_ptr_2)->WholePart) cmp_value = 1; - else if (m->WholePart < ((pMoneyType)data_ptr_2)->WholePart) cmp_value = -1; - else cmp_value = m->FractionPart - ((pMoneyType)data_ptr_2)->FractionPart; + if (m->Value > ((pMoneyType)data_ptr_2)->Value) cmp_value = 1; + else if (m->Value < ((pMoneyType)data_ptr_2)->Value) cmp_value = -1; + else cmp_value = 0; break; default: @@ -1870,8 +1872,9 @@ objDataToWords(int data_type, void* data_ptr) static char* teens[9] = { "Eleven","Twelve","Thirteen","Fourteen","Fifteen","Sixteen","Seventeen","Eighteen","Nineteen" }; static char* tens[9] = { "Ten","Twenty","Thirty","Forty","Fifty","Sixty","Seventy","Eighty","Ninety" }; static char* multiples[] = { "", "Thousand","Million","Billion","Trillion","Quadrillion" }; - unsigned long integer_part, fraction_part = 0; - int multiple_cnt, n, i; + unsigned long long integer_part, fraction_part = 0; + long long n; + int multiple_cnt, i; pMoneyType m; char nbuf[16]; @@ -1899,24 +1902,17 @@ objDataToWords(int data_type, void* data_ptr) else if (data_type == DATA_T_MONEY) { m = (pMoneyType)data_ptr; - if (m->WholePart < 0) + if (m->Value < 0) { - if (m->FractionPart == 0) - { - integer_part = -m->WholePart; - fraction_part = 0; - } - else - { - integer_part = (-m->WholePart) - 1; - fraction_part = 10000 - m->FractionPart; - } + /** Keep the opposite (positive) value for printing **/ + integer_part = -m->Value/10000; + fraction_part = -m->Value%10000; xsConcatenate(&tmpbuf, "Negative ", -1); } else { - integer_part = m->WholePart; - fraction_part = m->FractionPart; + integer_part = m->Value/10000; + fraction_part = m->Value%10000; } } else @@ -2184,11 +2180,14 @@ obj_internal_BuildBinaryItem(char** item, int* itemlen, pExpression exp, pParamO break; case DATA_T_MONEY: - /** XOR 0x80000000 to convert to Offset Zero form. **/ - ((unsigned int*)tmp_buf)[0] = htonl(exp->Types.Money.WholePart ^ 0x80000000); - ((unsigned short*)tmp_buf)[2] = htons(exp->Types.Money.FractionPart); + /** XOR 0x8000000000000000 to convert to Offset Zero form. **/ + /** Offset Zero form is a signed number representation where all + ** values below a certain bit (usually most significant) are negative + ** and all values above are positive. It allows sorting based on raw bit value. + ** It essentially functions like 2's complement with the sign bit inversed.**/ + ((long long*)tmp_buf)[0] = bswap_64(exp->Types.Money.Value ^ 0x8000000000000000); *item = (char*)(tmp_buf); - *itemlen = 6; + *itemlen = 8; break; case DATA_T_DOUBLE: @@ -2474,3 +2473,28 @@ objDateAdd(pDateTime dt, int diff_sec, int diff_min, int diff_hr, int diff_day, return 0; } + +/*** Convert the given MoneyType into the old representation with separate + *** whole/fraction parts, then put it into the given pointer parameters. + ***/ +void +objGetOldRepresentationOfMoney(MoneyType money, long long* wholePart, unsigned short* fractionPart) +{ + /** + * In the old representation, whole = floor($amount) and fraction = $amount - floor($amount). This means that + * fraction is always positive and always counts towards the total $amount in a positive direction. + * So if there is a negative with a non-zero fraction part... + **/ + if (money.Value < 0 && money.Value%10000 != 0) + { + /** Add negative 1 then truncate fraction. (essentially floor() )**/ + *wholePart = (money.Value - 10000)/10000ll; + /** Since fraction always counts in a positive direction, it is inverted**/ + *fractionPart = 10000ll - abs(money.Value%10000); + } + else + { + *wholePart = money.Value / 10000ll; + *fractionPart = money.Value % 10000ll; + } +} diff --git a/centrallix/osdrivers/objdrv_dbl.c b/centrallix/osdrivers/objdrv_dbl.c index d1c311bf9..8d5e25adf 100755 --- a/centrallix/osdrivers/objdrv_dbl.c +++ b/centrallix/osdrivers/objdrv_dbl.c @@ -1627,6 +1627,7 @@ dbl_internal_ParseColumn(pDblColInf column, pObjData pod, char* data, char* row_ char dtbuf[32]; unsigned long long v; int i,f; + double decimalOffsetValue = 10000; switch(column->Type) { @@ -1649,19 +1650,16 @@ dbl_internal_ParseColumn(pDblColInf column, pObjData pod, char* data, char* row_ if (column->DecimalOffset) pod->Double /= pow(10, column->DecimalOffset); break; case DATA_T_MONEY: - if (dbl_internal_MappedCopy(ibuf, sizeof(ibuf), column, row_data) < 0) return -1; - v = strtoll(ibuf, NULL, 10); - f = 1; - for(i=0;iDecimalOffset;i++) f *= 10; - pod->Money = (pMoneyType)data; - pod->Money->WholePart = v/f; - v = (v/f)*f; - if (column->DecimalOffset <= 4) - for(i=column->DecimalOffset;i<4;i++) v *= 10; - else - for(i=4;iDecimalOffset;i++) v /= 10; - pod->Money->FractionPart = v; - break; + //decimalOffsetValue is originally 10000 to convert v to 10000ths of a dollar + //decimalOffsetValue is divided by 10, column->DecimalOffset times, + //keeping the decimal as a double in case it drops below 0 + //Finally, I multiple v by decimalOffsetValue to get my Money->Value + if (dbl_internal_MappedCopy(ibuf, sizeof(ibuf), column, row_data) < 0) return -1; + v = strtoll(ibuf, NULL, 10); + decimalOffsetValue /= pow(10, column->DecimalOffset); + pod->Money = (pMoneyType)data; + pod->Money->Value = (v*decimalOffsetValue)+ 0.1; + break; default: mssError(1, "DBL", "Bark! Unhandled data type for column '%s'", column->Name); return -1; diff --git a/centrallix/osdrivers/objdrv_fp.c b/centrallix/osdrivers/objdrv_fp.c index 62c1deb11..452a31008 100644 --- a/centrallix/osdrivers/objdrv_fp.c +++ b/centrallix/osdrivers/objdrv_fp.c @@ -1221,6 +1221,7 @@ fp_internal_ParseColumn(pFpColInf column, pObjData pod, char* data, char* row_da char dtbuf[32]; unsigned long long v; int i,f; + double decimalOffsetValue = 10000; switch(column->Type) { @@ -1243,18 +1244,15 @@ fp_internal_ParseColumn(pFpColInf column, pObjData pod, char* data, char* row_da if (column->DecimalOffset) pod->Double /= pow(10, column->DecimalOffset); break; case DATA_T_MONEY: + //decimalOffsetValue is originally 10000 to convert v to 10000ths of a dollar + //decimalOffsetValue is divided by 10, column->DecimalOffset times, + //keeping the decimal as a double in case it drops below 0 + //Finally, I multiple v by decimalOffsetValue to get my Money->Value if (fp_internal_MappedCopy(ibuf, sizeof(ibuf), column, row_data) < 0) return -1; v = strtoll(ibuf, NULL, 10); - f = 1; - for(i=0;iDecimalOffset;i++) f *= 10; + decimalOffsetValue /= pow(10, column->DecimalOffset); pod->Money = (pMoneyType)data; - pod->Money->WholePart = v/f; - v = (v/f)*f; - if (column->DecimalOffset <= 4) - for(i=column->DecimalOffset;i<4;i++) v *= 10; - else - for(i=4;iDecimalOffset;i++) v /= 10; - pod->Money->FractionPart = v; + pod->Money->Value = (v*decimalOffsetValue)+ 0.1; break; default: mssError(1, "FP", "Bark! Unhandled data type for column '%s'", column->Name); diff --git a/centrallix/osdrivers/objdrv_sybase.c b/centrallix/osdrivers/objdrv_sybase.c index 50d408797..80f59e8e7 100644 --- a/centrallix/osdrivers/objdrv_sybase.c +++ b/centrallix/osdrivers/objdrv_sybase.c @@ -686,6 +686,7 @@ sybd_internal_GetCxValue(void* ptr, int ut, pObjData val, int datatype) { int i,minus,n; unsigned int msl,lsl,divtmp; + long long ll_msl, ll_lsl; unsigned int days,fsec; float f; @@ -775,9 +776,7 @@ sybd_internal_GetCxValue(void* ptr, int ut, pObjData val, int datatype) { /** smallmoney, 4-byte **/ memcpy(&i, ptr, 4); - val->Money->WholePart = i/10000; - if (i < 0 && (i%10000) != 0) val->Money->WholePart--; - val->Money->FractionPart = i - (val->Money->WholePart*10000); + val->Money->Value = i; return 0; } else @@ -785,39 +784,12 @@ sybd_internal_GetCxValue(void* ptr, int ut, pObjData val, int datatype) /** normal 8-byte money **/ memcpy(&lsl, ptr+4, 4); memcpy(&msl, ptr, 4); - minus = 0; - if (msl >= 0x80000000) - { - /** Negate **/ - minus = 1; - msl = ~msl; - lsl = ~lsl; - if (lsl == 0xFFFFFFFF) msl++; - lsl++; - } - /** Long division, 16 bits = 1 "digit" **/ - divtmp = msl/10000; - n = divtmp; - msl -= divtmp*10000; - msl = (msl<<16) + (lsl>>16); - divtmp = msl/10000; - n = (n<<16) + divtmp; - msl -= divtmp*10000; - msl = (msl<<16) + (lsl & 0xFFFF); - divtmp = msl/10000; - n = (n<<16) + divtmp; - msl -= divtmp*10000; - val->Money->WholePart = n; - val->Money->FractionPart = msl; - if (minus) - { - val->Money->WholePart = -val->Money->WholePart; - if (val->Money->FractionPart > 0) - { - val->Money->WholePart--; - val->Money->FractionPart = 10000 - val->Money->FractionPart; - } - } + + /** msl is the most significant 32-bit value and needs to come before lsl + ** I do this by shifting msl left cast to a long long by 32 bits, and then OR it with lsl + **/ + val->Money->Value = (((long long)msl)<<32) | (long long)lsl; + return 0; } } diff --git a/centrallix/tests/helpers/mssErrorHelper.c b/centrallix/tests/helpers/mssErrorHelper.c new file mode 100644 index 000000000..af8a456dd --- /dev/null +++ b/centrallix/tests/helpers/mssErrorHelper.c @@ -0,0 +1,21 @@ +#include +#include +#include "testhelpers/mssErrorHelper.h" +#include "cxlib/xstring.h" + +int +mssErrorHelper_init() +{ + mssInitialize("altpasswd", "/usr/local/etc/centrallix/cxpasswd-test", "", 0, "test"); + return mssAuthenticate("cxtest", "cxtestpass"); +} + +int +mssErrorHelper_mostRecentErrorContains(char* message) +{ + pXString err = xsNew(); + mssStringError(err); + char* foundPtr = strstr(xsString(err), message); + xsFree(err); + return !(!foundPtr); +} \ No newline at end of file diff --git a/centrallix/tests/helpers/stdoutHelper.c b/centrallix/tests/helpers/stdoutHelper.c new file mode 100644 index 000000000..2d295dd9a --- /dev/null +++ b/centrallix/tests/helpers/stdoutHelper.c @@ -0,0 +1,41 @@ +#include +#include +#include "testhelpers/stdoutHelper.h" + +int stdoutHelper_stdoutCopy; +FILE *stdoutHelper_tmpfile; + +int +stdoutHelper_startCapture() +{ + // Redirect stdout to a file + fflush(stdout); + stdoutHelper_stdoutCopy = dup(STDOUT_FILENO); + stdoutHelper_tmpfile = fopen(".test-helper-stdout", "w+"); + dup2(fileno(stdoutHelper_tmpfile), STDOUT_FILENO); +} + +char* +stdoutHelper_stopCaptureAndGetContents() +{ + // Stop redirecting stdout + fflush(stdout); + dup2(stdoutHelper_stdoutCopy, STDOUT_FILENO); + close(stdoutHelper_stdoutCopy); + + // Read the contents of the file with stdout contents + char* contents = ""; + fseek(stdoutHelper_tmpfile, 0, SEEK_END); + long size = ftell(stdoutHelper_tmpfile); + if (size > 0) { + contents = malloc(size + 1); + rewind(stdoutHelper_tmpfile); + fread(contents, 1, size, stdoutHelper_tmpfile); + contents[size] = 0; + } + + // Close and remove the file and return its contents + fclose(stdoutHelper_tmpfile); + remove(".test-helper-stdout"); + return contents; +} \ No newline at end of file diff --git a/centrallix/tests/test_expfn_max_money.cmp b/centrallix/tests/test_expfn_max_money.cmp new file mode 100644 index 000000000..de207dca4 --- /dev/null +++ b/centrallix/tests/test_expfn_max_money.cmp @@ -0,0 +1 @@ +Attribute [max(:f_money)]: money $2147483647.99 diff --git a/centrallix/tests/test_expfn_max_money.to b/centrallix/tests/test_expfn_max_money.to new file mode 100644 index 000000000..f3222b935 --- /dev/null +++ b/centrallix/tests/test_expfn_max_money.to @@ -0,0 +1,3 @@ +##NAME max() function on money type + +query select 'max(:f_money)' = max(:f_money) from /tests/Datatypes.csv/rows diff --git a/centrallix/tests/test_expfn_min_money.cmp b/centrallix/tests/test_expfn_min_money.cmp new file mode 100644 index 000000000..52cfa9c0c --- /dev/null +++ b/centrallix/tests/test_expfn_min_money.cmp @@ -0,0 +1 @@ +Attribute [min(:f_money)]: money -$2147483647.99 diff --git a/centrallix/tests/test_expfn_min_money.to b/centrallix/tests/test_expfn_min_money.to new file mode 100644 index 000000000..e131c83e0 --- /dev/null +++ b/centrallix/tests/test_expfn_min_money.to @@ -0,0 +1,3 @@ +##NAME min() function on money type + +query select 'min(:f_money)' = min(:f_money) from /tests/Datatypes.csv/rows diff --git a/centrallix/tests/test_expfn_round_money.cmp b/centrallix/tests/test_expfn_round_money.cmp new file mode 100644 index 000000000..557029d0c --- /dev/null +++ b/centrallix/tests/test_expfn_round_money.cmp @@ -0,0 +1,7 @@ +Attribute [round($1.225, 2)]: money $1.23 +Attribute [round($1.224, 2)]: money $1.22 +Attribute [round($1.50, 0)]: money $2.00 +Attribute [round($1.40, 0)]: money $1.00 +Attribute [round($16.25, -1)]: money $20.00 +Attribute [round($12.25, -1)]: money $10.00 +Attribute [round($12.25)]: money $12.00 diff --git a/centrallix/tests/test_expfn_round_money.to b/centrallix/tests/test_expfn_round_money.to new file mode 100644 index 000000000..a468451ab --- /dev/null +++ b/centrallix/tests/test_expfn_round_money.to @@ -0,0 +1,9 @@ +##NAME round() function on money type + +query select 'round($1.225, 2)' = round($1.225, 2) +query select 'round($1.224, 2)' = round($1.224, 2) +query select 'round($1.50, 0)' = round($1.50, 0) +query select 'round($1.40, 0)' = round($1.40, 0) +query select 'round($16.25, -1)' = round($16.25, -1) +query select 'round($12.25, -1)' = round($12.25, -1) +query select 'round($12.25)' = round($12.25) \ No newline at end of file diff --git a/centrallix/tests/test_expfn_truncate_money.cmp b/centrallix/tests/test_expfn_truncate_money.cmp new file mode 100644 index 000000000..7d91a7200 --- /dev/null +++ b/centrallix/tests/test_expfn_truncate_money.cmp @@ -0,0 +1,7 @@ +Attribute [truncate($1.225, 2)]: money $1.22 +Attribute [truncate($1.224, 2)]: money $1.22 +Attribute [truncate($1.50, 0)]: money $1.00 +Attribute [truncate($1.40, 0)]: money $1.00 +Attribute [truncate($16.25, -1)]: money $10.00 +Attribute [truncate($12.25, -1)]: money $10.00 +Attribute [truncate($12.25)]: money $12.00 diff --git a/centrallix/tests/test_expfn_truncate_money.to b/centrallix/tests/test_expfn_truncate_money.to new file mode 100644 index 000000000..9e582803c --- /dev/null +++ b/centrallix/tests/test_expfn_truncate_money.to @@ -0,0 +1,9 @@ +##NAME truncate() function on money type + +query select 'truncate($1.225, 2)' = truncate($1.225, 2) +query select 'truncate($1.224, 2)' = truncate($1.224, 2) +query select 'truncate($1.50, 0)' = truncate($1.50, 0) +query select 'truncate($1.40, 0)' = truncate($1.40, 0) +query select 'truncate($16.25, -1)' = truncate($16.25, -1) +query select 'truncate($12.25, -1)' = truncate($12.25, -1) +query select 'truncate($12.25)' = truncate($12.25) \ No newline at end of file diff --git a/centrallix/tests/test_moneytype_expCompareExpressionValues.c b/centrallix/tests/test_moneytype_expCompareExpressionValues.c new file mode 100644 index 000000000..82430b465 --- /dev/null +++ b/centrallix/tests/test_moneytype_expCompareExpressionValues.c @@ -0,0 +1,35 @@ +#include "obj.h" +#include +#include +#include "expression.h" + +long long +test(char** name) +{ + *name = "moneytype_00 - expCompareExpressionValues"; + + pExpression exp1 = expAllocExpression(); + ObjData moneyData1; + MoneyType money1 = {70000}; + moneyData1.Money = &money1; + exp1 = expPodToExpression(&moneyData1, DATA_T_MONEY, exp1); + + pExpression exp2 = expAllocExpression(); + ObjData moneyData2; + MoneyType money2 = {70000}; + moneyData2.Money = &money2; + exp2 = expPodToExpression(&moneyData2, DATA_T_MONEY, exp2); + + // true case (exp1 == exp2) + assert(expCompareExpressionValues(exp1, exp2) == 1); + + // false case (exp1 != exp2) + money2.Value = 8000; + exp2 = expPodToExpression(&moneyData2, DATA_T_MONEY, exp2); + assert(expCompareExpressionValues(exp1, exp2) == 0); + + expFreeExpression(exp1); + expFreeExpression(exp2); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_expDumpExpression_00.c b/centrallix/tests/test_moneytype_expDumpExpression_00.c new file mode 100644 index 000000000..7266b8f20 --- /dev/null +++ b/centrallix/tests/test_moneytype_expDumpExpression_00.c @@ -0,0 +1,37 @@ +#include "obj.h" +#include +#include +#include "expression.h" +#include "cxlib/xstring.h" +#include "testhelpers/stdoutHelper.h" + +long long +test(char** name) +{ + *name = "moneytype_00 - expDumpExpression"; + + pExpression testExp = expAllocExpression(); + ObjData moneyData; + MoneyType money = {70000}; + moneyData.Money = &money; + pObjData moneyDataPtr = &moneyData; + + testExp = expPodToExpression(moneyDataPtr, DATA_T_MONEY, testExp); + + stdoutHelper_startCapture(); + expDumpExpression(testExp); + char* stdoutContents = stdoutHelper_stopCaptureAndGetContents(); + assert(strstr(stdoutContents, "MONEY = 70000, 0 child(ren), money=$7.00, NEW")); + + /** Money > INT_MAX **/ + money.Value = 18888888888888888; + testExp = expPodToExpression(moneyDataPtr, DATA_T_MONEY, testExp); + stdoutHelper_startCapture(); + expDumpExpression(testExp); + stdoutContents = stdoutHelper_stopCaptureAndGetContents(); + assert(strstr(stdoutContents, "MONEY = 18888888888888888, 0 child(ren), money=$1888888888888.88, NEW")); + + expFreeExpression(testExp); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_json_util.c b/centrallix/tests/test_moneytype_json_util.c new file mode 100644 index 000000000..857d23518 --- /dev/null +++ b/centrallix/tests/test_moneytype_json_util.c @@ -0,0 +1,37 @@ +#include "obj.h" +#include +#include +#include "json/json_util.h" + +long long +test(char** name) +{ + *name = "moneytype_00 - json_util"; + + // Positive + json_object *json = json_tokener_parse("{\"wholepart\": 7, \"fractionpart\": 5000}"); + MoneyType money = {0}; + assert(jutilGetMoneyObject(json, &money) == 0); + assert(money.Value == 75000); + + // Negative + json = json_tokener_parse("{\"wholepart\": -8, \"fractionpart\": 5000}"); + assert(jutilGetMoneyObject(json, &money) == 0); + assert(money.Value == -75000); + + // wholepart / fractionpart in different order + json = json_tokener_parse("{\"fractionpart\": 5000, \"wholepart\": 7}"); + assert(jutilGetMoneyObject(json, &money) == 0); + assert(money.Value == 75000); + + // Value > INT_MAX + json = json_tokener_parse("{\"wholepart\": 220000000, \"fractionpart\": 5000}"); + assert(jutilGetMoneyObject(json, &money) == 0); + assert(money.Value == 2200000005000ll); + + // Json objects with other attributes are not accepted + json = json_tokener_parse("{\"wholepart\": 7, \"fractionpart\": 5000, \"nope\": 0}"); + assert(jutilGetMoneyObject(json, &money) == -1); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_obfuscate_00.c b/centrallix/tests/test_moneytype_obfuscate_00.c new file mode 100644 index 000000000..fb76003d7 --- /dev/null +++ b/centrallix/tests/test_moneytype_obfuscate_00.c @@ -0,0 +1,75 @@ +#include "obj.h" +#include +#include +#include "expression.h" +#include "cxlib/xstring.h" +#include "obfuscate.h" +#include "testhelpers/mssErrorHelper.h" + +void +testObfuscationWIntegerMultiples() +{ + ObjData srcVal, dstVal; + long long moneyValue = 1100; + MoneyType money = {moneyValue}; + srcVal.Money = &money; + int numsToObfuscate = 50; + int obfuscated = 0; + int i; + + for (i = 0; i < numsToObfuscate; i++) + { + money.Value = moneyValue; + int rval = obfObfuscateData(&srcVal, &dstVal, DATA_T_MONEY, NULL, NULL, NULL, NULL, "V", "i", NULL, NULL); + assert(rval == 0); + if (dstVal.Money->Value != srcVal.Money->Value) { + obfuscated++; + } + moneyValue *= 2; + } + float percentObfuscated = (float)obfuscated / (float)numsToObfuscate; + assert(percentObfuscated > 0.70); +} + +void +testObfuscationWIntegerObfuscate() +{ + ObjData srcVal, dstVal; + long long moneyValue = 1100; + MoneyType money = {moneyValue}; + srcVal.Money = &money; + int i; + + for (i = 0; i < 50; i++) + { + money.Value = moneyValue; + int rval = obfObfuscateData(&srcVal, &dstVal, DATA_T_MONEY, NULL, NULL, NULL, NULL, "V", NULL, NULL, NULL); + assert(rval == 0); + assert(dstVal.Money->Value != srcVal.Money->Value); + moneyValue *= 2; + } +} + +void +testMoneyTooLarge() +{ + ObjData srcVal, dstVal; + MoneyType money = {250000000000000000ll}; + srcVal.Money = &money; + + mssErrorHelper_init(); + obfObfuscateData(&srcVal, &dstVal, DATA_T_MONEY, NULL, NULL, NULL, NULL, "V", NULL, NULL, NULL); + assert(mssErrorHelper_mostRecentErrorContains("Obfuscate not supported for Money Type greater than INT_MAX")); +} + +long long +test(char** name) +{ + *name = "moneytype_00 - obfuscate"; + + testObfuscationWIntegerMultiples(); + testObfuscationWIntegerObfuscate(); + testMoneyTooLarge(); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objBuildBinaryImage_00.c b/centrallix/tests/test_moneytype_objBuildBinaryImage_00.c new file mode 100644 index 000000000..dbf0e2c97 --- /dev/null +++ b/centrallix/tests/test_moneytype_objBuildBinaryImage_00.c @@ -0,0 +1,67 @@ +#include "obj.h" +#include +#include +#include "expression.h" +#include "cxlib/xstring.h" + +long long +test(char** name) +{ + *name = "moneytype_00 - objBuildBinaryImage"; + + pExpression testExp = expAllocExpression(); + pExpression testExpCmp = expAllocExpression(); + pParamObjects testParamObjects = expCreateParamList(); + + //Buffer must be at least 12 bytes for data types not yet implemented. + //Current highest byte size is 9, but 12 may come later + char buf[12]; + char cmpBuf[12]; + int buflen = 12; + + /** NULL Case **/ + assert(objBuildBinaryImage(buf, buflen, &testExp, 1, testParamObjects, 0) == -1); + + //For remaining cases, creating two ObjData with a moneytype inside, pObjData for parameters + //After passing through BuildBinaryImage, they will be compared with memcmp() because it is not the binary value + //itself that matters, but the comparison between the two objects. + ObjData moneyData; + MoneyType money = {70000}; + moneyData.Money = &money; + pObjData moneyDataPtr = &moneyData; + + ObjData moneyDataCmp; + MoneyType moneyCmp = {65000}; + moneyDataCmp.Money = &moneyCmp; + pObjData moneyDataPtrCmp = &moneyDataCmp; + + /** Positive Case **/ + testExp = expPodToExpression(moneyDataPtr, DATA_T_MONEY, testExp); + testExpCmp = expPodToExpression(moneyDataPtrCmp, DATA_T_MONEY, testExpCmp); + objBuildBinaryImage(buf, buflen, &testExp, 1, testParamObjects, 0); + objBuildBinaryImage(cmpBuf, buflen, &testExpCmp, 1, testParamObjects, 0); + assert(memcmp(buf,cmpBuf,8) > 0); + + /** Negative Case **/ + money.Value = -70000; + moneyCmp.Value = -65000; + testExp = expPodToExpression(moneyDataPtr, DATA_T_MONEY, testExp); + testExpCmp = expPodToExpression(moneyDataPtrCmp, DATA_T_MONEY, testExpCmp); + objBuildBinaryImage(buf, buflen, &testExp, 1, testParamObjects, 0); + objBuildBinaryImage(cmpBuf, buflen, &testExpCmp, 1, testParamObjects, 0); + assert(memcmp(buf,cmpBuf,8) < 0); + + /** Money > INT_MAX Case **/ + money.Value = 18888888888888888; + moneyCmp.Value = 17777777777777777; + testExp = expPodToExpression(moneyDataPtr, DATA_T_MONEY, testExp); + testExpCmp = expPodToExpression(moneyDataPtrCmp, DATA_T_MONEY, testExpCmp); + objBuildBinaryImage(buf, buflen, &testExp, 1, testParamObjects, 0); + objBuildBinaryImage(cmpBuf, buflen, &testExpCmp, 1, testParamObjects, 0); + assert(memcmp(buf,cmpBuf,8) > 0); + + expFreeExpression(testExp); + expFreeExpression(testExpCmp); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objDataCompare_00.c b/centrallix/tests/test_moneytype_objDataCompare_00.c new file mode 100644 index 000000000..8f70a1ee8 --- /dev/null +++ b/centrallix/tests/test_moneytype_objDataCompare_00.c @@ -0,0 +1,89 @@ +#include "obj.h" +#include +#include +//#include + +long long +test(char** name) +{ + *name = "moneytype_00 - objDataCompare"; + MoneyType test = {70000}; + + /** Compare Int with Money Case **/ + /** Greater Than **/ + int testInt = 8; + assert(objDataCompare(DATA_T_INTEGER, &testInt, DATA_T_MONEY, &test) == 1); + /** Less Than **/ + testInt = 6; + assert(objDataCompare(DATA_T_INTEGER, &testInt, DATA_T_MONEY, &test) == -1); + /** Equal To **/ + testInt = 7; + assert(objDataCompare(DATA_T_INTEGER, &testInt, DATA_T_MONEY, &test) == 0); + /** Int multiplied to be > INT_MAX **/ + testInt = 2147483647; + assert(objDataCompare(DATA_T_INTEGER, &testInt, DATA_T_MONEY, &test) == 1); + + /** Compare Double with Money Case **/ + /** Greater Than **/ + double testDouble = 7.5; + assert(objDataCompare(DATA_T_DOUBLE, &testDouble, DATA_T_MONEY, &test) == 1); + /** Less Than **/ + testDouble = 5.5; + assert(objDataCompare(DATA_T_DOUBLE, &testDouble, DATA_T_MONEY, &test) == -1); + /** Equal To **/ + testDouble = 7.0; + assert(objDataCompare(DATA_T_DOUBLE, &testDouble, DATA_T_MONEY, &test) == 0); + + /** Compare String with Money Case **/ + /** Greater Than **/ + char data_ptr[] = "$8.50"; + assert(objDataCompare(DATA_T_STRING, data_ptr, DATA_T_MONEY, &test) == 1); + /** Less Than **/ + char data_ptr2[] = "$4.50"; + assert(objDataCompare(DATA_T_STRING, data_ptr2, DATA_T_MONEY, &test) == -1); + /** Equal To **/ + char data_ptr3[] = "$7.00"; + assert(objDataCompare(DATA_T_STRING, data_ptr3, DATA_T_MONEY, &test) == 0); + + /** Compare Money with Money Case **/ + /** Greater Than **/ + MoneyType testMoney = {80500}; + assert(objDataCompare(DATA_T_MONEY, &testMoney, DATA_T_MONEY, &test) == 1); + /** Less Than **/ + testMoney.Value = 50000; + assert(objDataCompare(DATA_T_MONEY, &testMoney, DATA_T_MONEY, &test) == -1); + /** Equal To **/ + testMoney.Value = 70000; + assert(objDataCompare(DATA_T_MONEY, &testMoney, DATA_T_MONEY, &test) == 0); + + /** Compare DateTime with Money Case **/ + DateTime testDate = {0}; + assert(objDataCompare(DATA_T_DATETIME, &testDate, DATA_T_MONEY, &test) == -2); + + /** Compare IntV with Money Case **/ + /** IntV fails with any value other than 2 **/ + IntVec testIV = {0}; + assert(objDataCompare(DATA_T_INTVEC, &testIV, DATA_T_MONEY, &test) == -2); + + int* argArray[2]; + IntVec validTestIV = {0}; + validTestIV.nIntegers = 2; + validTestIV.Integers = argArray; + validTestIV.Integers[0] = 9; + validTestIV.Integers[1] = 20; + assert(objDataCompare(DATA_T_INTVEC, &validTestIV, DATA_T_MONEY, &test) == 1); + + validTestIV.Integers[0] = 4; + validTestIV.Integers[1] = 35; + assert(objDataCompare(DATA_T_INTVEC, &validTestIV, DATA_T_MONEY, &test) == -1); + + validTestIV.Integers[0] = 7; + validTestIV.Integers[1] = 0; + assert(objDataCompare(DATA_T_INTVEC, &validTestIV, DATA_T_MONEY, &test) == 0); + + /** Compare StrV with Money Case **/ + StringVec testSV = {0}; + assert(objDataCompare(DATA_T_STRINGVEC, &testSV, DATA_T_MONEY, &test) == -2); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objDataToDouble_00.c b/centrallix/tests/test_moneytype_objDataToDouble_00.c new file mode 100644 index 000000000..6b86ea384 --- /dev/null +++ b/centrallix/tests/test_moneytype_objDataToDouble_00.c @@ -0,0 +1,26 @@ +#include "obj.h" +#include + +long long +test(char** name) +{ + *name = "moneytype_00 - objDataToDouble"; + + /** Zero Case **/ + MoneyType test = {0}; + assert(objDataToDouble(7,&test) == 0.0); + + /** Positive Case **/ + test.Value = 10000; + assert(objDataToDouble(7,&test) == 1.0); + + /** Negative Case **/ + test.Value = -10000; + assert(objDataToDouble(7,&test) == -1.0); + + /** Rational Case **/ + test.Value = -25000; + assert(objDataToDouble(7,&test) == -2.5); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objDataToInteger_00.c b/centrallix/tests/test_moneytype_objDataToInteger_00.c new file mode 100644 index 000000000..c42f627f9 --- /dev/null +++ b/centrallix/tests/test_moneytype_objDataToInteger_00.c @@ -0,0 +1,35 @@ +#include +#include +#include "obj.h" +#include "testhelpers/mssErrorHelper.h" + +long long +test(char** name) +{ + *name = "moneytype_00 - objDataToInteger"; + + /** Zero Case **/ + MoneyType test = {0}; + assert(objDataToInteger(7,&test,NULL) == 0); + + /** Positive Case **/ + test.Value = 10000; + assert(objDataToInteger(7,&test,NULL) == 1); + + /** Negative Case **/ + test.Value = -10000; + assert(objDataToInteger(7,&test,NULL) == -1); + + /** Fractional Case **/ + test.Value = 15000; + assert(objDataToInteger(7,&test,NULL) == 1); + + /** Overflow Case **/ + mssErrorHelper_init(); + test.Value = 250000000000000000; + objDataToInteger(7,&test,NULL); + assert(mssErrorHelper_mostRecentErrorContains( + "OBJ: Warning: 7 (MoneyType) overflow; cannot fit value of that size in int")); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objDataToMoney_00.c b/centrallix/tests/test_moneytype_objDataToMoney_00.c new file mode 100644 index 000000000..39aa2231a --- /dev/null +++ b/centrallix/tests/test_moneytype_objDataToMoney_00.c @@ -0,0 +1,74 @@ +#include "obj.h" +#include +#include +#include + +long long +test(char** name) +{ + *name = "moneytype_00 - objDataToMoney"; + MoneyType test = {0}; + + /** String Case **/ + char data_ptr[20] = "$4.50"; + assert(objDataToMoney(2,data_ptr,&test) == 0); + assert(test.Value == 45000); + + strcpy(data_ptr, "-$4.50"); + assert(objDataToMoney(2,data_ptr,&test) == 0); + assert(test.Value == -45000); + + strcpy(data_ptr, "-$0.01"); + assert(objDataToMoney(2,data_ptr,&test) == 0); + assert(test.Value == -100); + + /** String Case that would overflow INT_MAX and INT_MIN **/ + strcpy(data_ptr, "$2200000000.00"); + assert(objDataToMoney(2,data_ptr,&test) == 0); + assert(test.Value == 22000000000000); + + strcpy(data_ptr, "-$2200000000.00"); + assert(objDataToMoney(2,data_ptr,&test) == 0); + assert(test.Value == -22000000000000); + + /** Overflow Case (intval > LL max) **/ + char overflow_ptr[] = "$10000000000000000000.50"; + assert(objDataToMoney(2,overflow_ptr,&test) == -1); + + /** Double Case **/ + double testDouble = 5.5; + assert(objDataToMoney(3,&testDouble,&test) == 0); + assert(test.Value == 55000); + + testDouble = -5.5; + assert(objDataToMoney(3,&testDouble,&test) == 0); + assert(test.Value == -55000); + + /** Double Case that would overflow INT_MAX and INT_MIN **/ + testDouble = 2200000000.5; + assert(objDataToMoney(3,&testDouble,&test) == 0); + assert(test.Value == 22000000005000); + + testDouble = -2200000000.5; + assert(objDataToMoney(3,&testDouble,&test) == 0); + assert(test.Value == -22000000005000); + + /** Double Fraction Overflow **/ + testDouble = 5.159999999999; + assert(objDataToMoney(3,&testDouble,&test) == 0); + assert(test.Value == 51600); + + /** Int Case **/ + int testInt = 6; + assert(objDataToMoney(1,&testInt,&test) == 0); + assert(test.Value == 60000); + + /** Money Case **/ + MoneyType testMoney = {900000}; + assert(objDataToMoney(7,&testMoney,&test) == 0); + assert(test.Value == 900000); + + + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objDataToWords_00.c b/centrallix/tests/test_moneytype_objDataToWords_00.c new file mode 100644 index 000000000..b18e78b0a --- /dev/null +++ b/centrallix/tests/test_moneytype_objDataToWords_00.c @@ -0,0 +1,44 @@ +#include "obj.h" +#include +#include +#include "expression.h" +#include "cxlib/xstring.h" + +long long +test(char** name) +{ + *name = "moneytype_00 - objDataToWords"; + MoneyType test = {70500}; + + /** Positive Case **/ + char* data_ptr = "Seven And 05/100 "; + char* returnStr = objDataToWords(DATA_T_MONEY, &test); + assert(strcmp(data_ptr, returnStr) == 0); + + /** Negative Case **/ + test.Value = -70500; + data_ptr = "Negative Seven And 05/100 "; + returnStr = objDataToWords(DATA_T_MONEY, &test); + assert(strcmp(data_ptr, returnStr) == 0); + + /** Money > INT_MAX **/ + test.Value = 18888888888888888; + data_ptr = "One Trillion Eight Hundred Eighty-Eight Billion, Eight Hundred Eighty-Eight Million, Eight Hundred Eighty-Eight Thousand, Eight Hundred Eighty-Eight And 88/100 "; + returnStr = objDataToWords(DATA_T_MONEY, &test); + assert(strcmp(data_ptr, returnStr) == 0); + + /**objDataToWords Truncates fractional cent values past 100ths**/ + /** Fractional Case **/ + test.Value = -70525; + data_ptr = "Negative Seven And 05/100 "; + returnStr = objDataToWords(DATA_T_MONEY, &test); + assert(strcmp(data_ptr, returnStr) == 0); + + /** Fractional Case **/ + test.Value = -70575; + data_ptr = "Negative Seven And 05/100 "; + returnStr = objDataToWords(DATA_T_MONEY, &test); + assert(strcmp(data_ptr, returnStr) == 0); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objFormatMoneyTmp_00.c b/centrallix/tests/test_moneytype_objFormatMoneyTmp_00.c new file mode 100644 index 000000000..d8d1da6d5 --- /dev/null +++ b/centrallix/tests/test_moneytype_objFormatMoneyTmp_00.c @@ -0,0 +1,34 @@ +#include "obj.h" +#include + +long long +test(char** name) +{ + *name = "moneytype_00 - objFormatMoneyTmp"; + + /** 0 Case **/ + MoneyType test = {0}; + assert(strcmp(objFormatMoneyTmp(&test, NULL), "$0.00") == 0); + + /** Positive Case **/ + test.Value = 70000; + assert(strcmp(objFormatMoneyTmp(&test, NULL), "$7.00") == 0); + + /** Negative Case **/ + test.Value = -70000; + assert(strcmp(objFormatMoneyTmp(&test, NULL), "-$7.00") == 0); + + /** Nonzero Cent Case **/ + test.Value = -75000; + assert(strcmp(objFormatMoneyTmp(&test, NULL), "-$7.50") == 0); + + /** Fractional Cent Case **/ + test.Value = -70001; + assert(strcmp(objFormatMoneyTmp(&test, NULL), "-$7.00") == 0); + + /** Whole Part > INT_MAX **/ + test.Value = 1836475854449306500; + assert(strcmp(objFormatMoneyTmp(&test, NULL), "$183647585444930.65") == 0); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objGetOldRepresentationOfMoney.c b/centrallix/tests/test_moneytype_objGetOldRepresentationOfMoney.c new file mode 100644 index 000000000..55db44e57 --- /dev/null +++ b/centrallix/tests/test_moneytype_objGetOldRepresentationOfMoney.c @@ -0,0 +1,34 @@ +#include "obj.h" +#include + +long long +test(char** name) +{ + *name = "moneytype_00 - objGetOldRepresentationOfMoney"; + + /* Positive case */ + MoneyType money = {75000}; + long long wholePart = 0; + unsigned short fractionPart = 0; + objGetOldRepresentationOfMoney(money, &wholePart, &fractionPart); + assert(wholePart == 7); + assert(fractionPart == 5000); + + /* Negative with non-zero fraction part */ + money.Value = -75000; + wholePart = 0; + fractionPart = 0; + objGetOldRepresentationOfMoney(money, &wholePart, &fractionPart); + assert(wholePart == -8); + assert(fractionPart == 5000); + + /* Handles money.Value larger than INT_MAX */ + money.Value = 250000000000005000; + wholePart = 0; + fractionPart = 0; + objGetOldRepresentationOfMoney(money, &wholePart, &fractionPart); + assert(wholePart == 25000000000000); + assert(fractionPart == 5000); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objdrv_dbl.c b/centrallix/tests/test_moneytype_objdrv_dbl.c new file mode 100644 index 000000000..e65d232a6 --- /dev/null +++ b/centrallix/tests/test_moneytype_objdrv_dbl.c @@ -0,0 +1,52 @@ +#include "obj.h" +#include +#include +#include "expression.h" +#include "cxlib/xstring.h" +#include "osdrivers/objdrv_dbl.c" + +long long +test(char** name) +{ + *name = "moneytype_00 - objdrv_dbl"; + + //This test is making the assumption that the raw data passed in testString here (row_data in objdrv_dbl.c) + //is in units of dollars (and so dbl_internal_ParseColumn should convert it to 1/10000ths of a dollar when putting + //it into a MoneyType representation) + + DblColInf testColInf; + pDblColInf pTestColInf = &testColInf; + testColInf.Type = DATA_T_MONEY; + testColInf.Length = 8; + unsigned short byteMap[] = {0}; + testColInf.ByteMap = byteMap; + testColInf.DecimalOffset = 0; + + ObjData moneyData; + pObjData moneyDataPtr = &moneyData; + MoneyType testMoneyData = {0}; + pMoneyType pTestMoneyData = &testMoneyData; + + /** No Decimal Offset Case **/ + char* rowData = "450"; + assert(dbl_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, rowData) == 0); + assert(moneyDataPtr->Money->Value == 4500000); + + /** Decimal Offset, multiplier > 1 case **/ + testColInf.DecimalOffset = 1; + assert(dbl_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, rowData) == 0); + assert(moneyDataPtr->Money->Value == 450000); + + /** Decimal Offset, multiplier < 1 case **/ + testColInf.DecimalOffset = 5; + assert(dbl_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, rowData) == 0); + assert(moneyDataPtr->Money->Value == 45); + + /** Rounding for floating point error handling **/ + char* testRoundedString = "45011999"; + testColInf.DecimalOffset = 5; + assert(dbl_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, testRoundedString) == 0); + assert(moneyDataPtr->Money->Value == 4501200); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objdrv_fp.c b/centrallix/tests/test_moneytype_objdrv_fp.c new file mode 100644 index 000000000..73039fb9d --- /dev/null +++ b/centrallix/tests/test_moneytype_objdrv_fp.c @@ -0,0 +1,51 @@ +#include "obj.h" +#include +#include +#include "expression.h" +#include "cxlib/xstring.h" +#include "osdrivers/objdrv_fp.c" + +long long +test(char** name) +{ + *name = "moneytype_00 - objdrv_fp"; + + //This test is making the assumption that the raw data passed in testString here (row_data in objdrv_fp.c) + //is in units of dollars (and so dbl_internal_ParseColumn should convert it to 1/10000ths of a dollar when putting + //it into a MoneyType representation) + + FpColInf testColInf; + pFpColInf pTestColInf = &testColInf; + + testColInf.Type = DATA_T_MONEY; + testColInf.Length = 8; + testColInf.RecordOffset = 0; + testColInf.DecimalOffset = 0; + ObjData moneyData; + pObjData moneyDataPtr = &moneyData; + MoneyType testMoneyData = {0}; + pMoneyType pTestMoneyData = &testMoneyData; + + /** No Decimal Offset Case **/ + char* rowData = "450"; + assert(fp_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, rowData) == 0); + assert(moneyDataPtr->Money->Value == 4500000); + + /** Decimal Offset, multiplier > 1 case **/ + testColInf.DecimalOffset = 1; + assert(fp_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, rowData) == 0); + assert(moneyDataPtr->Money->Value == 450000); + + /** Decimal Offset, multiplier < 1 case **/ + testColInf.DecimalOffset = 5; + assert(fp_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, rowData) == 0); + assert(moneyDataPtr->Money->Value == 45); + + /** Rounding for floating point error handling **/ + char* testRoundedString = "45011999"; + testColInf.DecimalOffset = 5; + assert(fp_internal_ParseColumn(pTestColInf, moneyDataPtr, (char*)pTestMoneyData, testRoundedString) == 0); + assert(moneyDataPtr->Money->Value == 4501200); + + return 0; +} diff --git a/centrallix/tests/test_moneytype_objdrv_sybase.c b/centrallix/tests/test_moneytype_objdrv_sybase.c new file mode 100644 index 000000000..dbe6d70c1 --- /dev/null +++ b/centrallix/tests/test_moneytype_objdrv_sybase.c @@ -0,0 +1,42 @@ +#include "config.h" +#ifdef USE_SYBASE +#include "obj.h" +#include +#include "osdrivers/objdrv_sybase.c" +#endif + +long long +test(char** name) +{ + *name = "moneytype_00 - objdrv_sybase"; + + #ifdef USE_SYBASE + MoneyType moneyResult; + ObjData objDataResult; + objDataResult.Money = &moneyResult; + + /** smallmoney **/ + int smallMoneyType = 21; + int smallMoneyToConvert = 45000; + assert(sybd_internal_GetCxValue(&smallMoneyToConvert, smallMoneyType, &objDataResult, DATA_T_MONEY) == 0); + assert(objDataResult.Money->Value == 45000); + + /** 8-byte money **/ + int eightByteMoneyType = 11; + //Sybase 8 byte money is mixed endian: 0 = least significant bit, f = most significant bit + unsigned long long moneyToConvert = 0x76543210fedcba98; + assert(sybd_internal_GetCxValue(&moneyToConvert, eightByteMoneyType, &objDataResult, DATA_T_MONEY) == 0); + //mixed-endianness should be fixed now + assert(objDataResult.Money->Value == 0xfedcba9876543210); + + /** confirm positive 8-byte money works as well **/ + moneyToConvert = 0xfedcba9876543210; + assert(sybd_internal_GetCxValue(&moneyToConvert, eightByteMoneyType, &objDataResult, DATA_T_MONEY) == 0); + assert(objDataResult.Money->Value == 0x76543210fedcba98); + + return 0; + + #else + return 1; + #endif +} diff --git a/centrallix/utility/json_util.c b/centrallix/utility/json_util.c index ce4b678d3..eb1400d48 100644 --- a/centrallix/utility/json_util.c +++ b/centrallix/utility/json_util.c @@ -228,6 +228,7 @@ jutilGetMoneyObject(struct json_object* jobj, pMoneyType m) /** Search the object's properties **/ memset(m, 0, sizeof(MoneyType)); + m->Value = 0; json_object_object_foreachC(jobj, iter) { if (json_object_is_type(iter.val, json_type_int)) @@ -235,12 +236,12 @@ jutilGetMoneyObject(struct json_object* jobj, pMoneyType m) if (!strcmp(iter.key, "wholepart")) { has_whole = 1; - m->WholePart = json_object_get_int(iter.val); + m->Value += json_object_get_int(iter.val) * 10000ll; } else if (!strcmp(iter.key, "fractionpart")) { has_fraction = 1; - m->FractionPart = json_object_get_int(iter.val); + m->Value += json_object_get_int(iter.val); } else { diff --git a/centrallix/utility/obfuscate.c b/centrallix/utility/obfuscate.c index 75eb8cea0..3f0f020a3 100644 --- a/centrallix/utility/obfuscate.c +++ b/centrallix/utility/obfuscate.c @@ -1140,8 +1140,10 @@ obfObfuscateData(pObjData srcval, pObjData dstval, int data_type, char* attrname static char* str = NULL; int bitcnt = 0; int iv, dv; + long long lliv, lldv; int scale; XString xs; + long long twoDecimalPlacesMoney; /** Empty param? **/ if (!param) @@ -1200,18 +1202,25 @@ obfObfuscateData(pObjData srcval, pObjData dstval, int data_type, char* attrname break; case DATA_T_MONEY: - iv = srcval->Money->WholePart * 100 + (srcval->Money->FractionPart / 100); - if (strchr(param,'i')) - dv = obf_internal_ObfuscateIntegerMultiples(hash, hash_novalue, &bitcnt, iv); - else - { - if (obf_internal_GetBits(hash, &bitcnt, 1)) iv = (iv + 1)*10; - if (obf_internal_GetBits(hash, &bitcnt, 1)) iv = iv/10 + 1; - dv = obf_internal_ObfuscateInteger(hash, hash_novalue, &bitcnt, iv); - } - m.WholePart = floor(dv/100.0); - m.FractionPart = (dv - m.WholePart*100) * 100; - dstval->Money = &m; + twoDecimalPlacesMoney = srcval->Money->Value / 100; + if (twoDecimalPlacesMoney <= INT_MAX && twoDecimalPlacesMoney >= INT_MIN) + { + iv = twoDecimalPlacesMoney; + if (strchr(param,'i')) + dv = obf_internal_ObfuscateIntegerMultiples(hash, hash_novalue, &bitcnt, iv); + else + { + if (obf_internal_GetBits(hash, &bitcnt, 1)) iv = (iv + 1)*10; + if (obf_internal_GetBits(hash, &bitcnt, 1)) iv = iv/10 + 1; + dv = obf_internal_ObfuscateInteger(hash, hash_novalue, &bitcnt, iv); + } + m.Value = (long long)(dv * 100); + dstval->Money = &m; + } + else + { + mssError(1,"OBF","Obfuscate not supported for Money Type greater than INT_MAX"); + } break; case DATA_T_DOUBLE: