1 // Written in D programming language
2 /**
3 *   PostgreSQL numeric format
4 *
5 *   Copyright: © 2014 DSoftOut
6 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
7 *   Authors: NCrashed <ncrashed@gmail.com>
8 */
9 module pgator.db.pq.types.numeric;
10 
11 import pgator.db.pq.types.oids;
12 import pgator.util..string;
13 import vibe.data.json;
14 import std.bitmanip;
15 import std.algorithm;
16 import std.array;
17 import std.format;
18 import std.conv;
19 import std.exception;
20 import std.bigint;
21 import std.range;
22 import core.memory;
23 
24 private // inner representation from libpq sources
25 {
26 	alias ushort NumericDigit;
27     enum DEC_DIGITS = 4;
28     enum NUMERIC_NEG       = 0x4000;
29     enum NUMERIC_NAN       = 0xC000;
30     
31     struct NumericVar
32     {
33     	int			weight;
34     	int 		sign;
35     	int			dscale;
36     	NumericDigit[] digits;
37     }
38     
39     string numeric_out(ref NumericVar num)
40     {
41     	string str;
42     	
43     	if(num.sign == NUMERIC_NAN)
44     	{
45     		return "NaN";
46     	}
47     	
48     	str = get_str_from_var(num);
49     	
50     	return str;
51     }
52     
53 	/*
54 	 * get_str_from_var() -
55 	 *
56 	 *  Convert a var to text representation (guts of numeric_out).
57 	 *  The var is displayed to the number of digits indicated by its dscale.
58 	 *  Returns a palloc'd string.
59 	 */
60 	string get_str_from_var(ref NumericVar var)
61 	{
62 	    int         dscale;
63 	    char*       str;
64 	    char*       cp;
65 	    char*       endcp;
66 	    int         i;
67 	    int         d;
68 	    NumericDigit dig;
69 	
70 		static if(DEC_DIGITS > 1)
71 		{
72 			NumericDigit d1;
73 		}
74 	
75 	    dscale = var.dscale;
76 	
77 	    /*
78 	     * Allocate space for the result.
79 	     *
80 	     * i is set to the # of decimal digits before decimal point. dscale is the
81 	     * # of decimal digits we will print after decimal point. We may generate
82 	     * as many as DEC_DIGITS-1 excess digits at the end, and in addition we
83 	     * need room for sign, decimal point, null terminator.
84 	     */
85 	    i = (var.weight + 1) * DEC_DIGITS;
86 	    if (i <= 0)
87 	        i = 1;
88 	        
89 	    str = cast(char*)GC.malloc(i + dscale + DEC_DIGITS + 2);
90 	    cp = str;
91 	
92 	    /*
93 	     * Output a dash for negative values
94 	     */
95 	    if (var.sign == NUMERIC_NEG)
96 	        *cp++ = '-';
97 	
98 	    /*
99 	     * Output all digits before the decimal point
100 	     */
101 	    if (var.weight < 0)
102 	    {
103 	        d = var.weight + 1;
104 	        *cp++ = '0';
105 	    }
106 	    else
107 	    {
108 	        for (d = 0; d <= var.weight; d++)
109 	        {
110 	            dig = (d < var.digits.length) ? var.digits[d] : 0;
111 	            /* In the first digit, suppress extra leading decimal zeroes */
112 	            static if(DEC_DIGITS == 4)
113 	            {
114 	                bool putit = (d > 0);
115 	
116 	                d1 = dig / 1000;
117 	                dig -= d1 * 1000;
118 	                putit |= (d1 > 0);
119 	                if (putit)
120 	                    *cp++ = cast(char)(d1 + '0');
121 	                d1 = dig / 100;
122 	                dig -= d1 * 100;
123 	                putit |= (d1 > 0);
124 	                if (putit)
125 	                    *cp++ = cast(char)(d1 + '0');
126 	                d1 = dig / 10;
127 	                dig -= d1 * 10;
128 	                putit |= (d1 > 0);
129 	                if (putit)
130 	                    *cp++ = cast(char)(d1 + '0');
131 	                *cp++ = cast(char)(dig + '0');
132 	            }
133 	            else static if(DEC_DIGITS == 2)
134 	            {
135 		            d1 = dig / 10;
136 		            dig -= d1 * 10;
137 		            if (d1 > 0 || d > 0)
138 		                *cp++ = cast(char)(d1 + '0');
139 		            *cp++ = cast(char)(dig + '0');
140 	            }
141 	            else static if(DEC_DIGITS == 1)
142 	            {
143 	            	*cp++ = cast(char)(dig + '0');
144 	            }
145 	            else pragma(error, "unsupported NBASE");
146 	        }
147 	    }
148 	
149 	    /*
150 	     * If requested, output a decimal point and all the digits that follow it.
151 	     * We initially put out a multiple of DEC_DIGITS digits, then truncate if
152 	     * needed.
153 	     */
154 	    if (dscale > 0)
155 	    {
156 	        *cp++ = '.';
157 	        endcp = cp + dscale;
158 	        for (i = 0; i < dscale; d++, i += DEC_DIGITS)
159 	        {
160 	            dig = (d >= 0 && d < var.digits.length) ? var.digits[d] : 0;
161 	            static if(DEC_DIGITS == 4)
162 	            {
163 		            d1 = dig / 1000;
164 		            dig -= d1 * 1000;
165 		            *cp++ = cast(char)(d1 + '0');
166 		            d1 = dig / 100;
167 		            dig -= d1 * 100;
168 		            *cp++ = cast(char)(d1 + '0');
169 		            d1 = dig / 10;
170 		            dig -= d1 * 10;
171 		            *cp++ = cast(char)(d1 + '0');
172 		            *cp++ = cast(char)(dig + '0');
173 	            }
174 	            else static if(DEC_DIGITS == 2)
175 	            {
176 		            d1 = dig / 10;
177 		            dig -= d1 * 10;
178 		            *cp++ = cast(char)(d1 + '0');
179 		            *cp++ = cast(char)(dig + '0');
180 	            }
181 	            else static if(DEC_DIGITS == 1)
182 	            {
183 	            	*cp++ = cast(char)(dig + '0');
184             	}
185             	else pragma(error, "unsupported NBASE");
186 	        }
187 	        cp = endcp;
188 	    }
189 	
190 	    /*
191 	     * terminate the string and return it
192 	     */
193 	    *cp = '\0';
194 	    return str.fromStringz.idup;
195 	}
196 }
197 
198 struct PGNumeric 
199 {
200 	string payload;
201 	
202     /**
203     *   If the numeric fits double boundaries, stores it 
204     *   into $(B val) and returns true, else returns false
205     *   and fills $(B val) with NaN.
206     */
207     bool canBeNative(out double val) const
208     {
209         try
210         {
211             val = payload.to!double;
212             
213             auto builder = appender!string;
214             formattedWrite(builder, "%."~payload.find('.').length.to!string~"f", val);
215             enforce(builder.data.strip('0') == payload);
216         } catch(Exception e)
217         {
218             val = double.nan;
219             return false;
220         }
221         return true;
222     }
223     
224     void toString(scope void delegate(const(char)[]) sink) const
225     {
226     	sink(payload);
227     }
228     
229     static PGNumeric fromString(string src)
230     {
231         return PGNumeric(src);
232     }
233     
234     Json toJson() const
235     {
236         double val;
237         if(canBeNative(val))
238         {
239             return Json(val);
240         } else
241         {
242             return Json(payload);
243         }
244     }
245     
246     static PGNumeric fromJson(Json src)
247     {
248         switch(src.type)
249         {
250             case(Json.Type.float_): return PGNumeric(src.get!double.to!string);
251             case(Json.Type.int_): return PGNumeric(src.get!long.to!string);
252             case(Json.Type..string): return PGNumeric(src.get!string);
253             default: throw new Exception(text("Cannot convert ", src.type, " to PGNumeric!"));
254         }
255     }
256 }
257 
258 PGNumeric convert(PQType type)(ubyte[] val)
259     if(type == PQType.Numeric)
260 {
261 	assert(val.length >= 4*ushort.sizeof);
262 	
263 	NumericVar	value;
264 	val.read!ushort; // num of digits
265 	value.weight = val.read!short;
266 	value.sign = val.read!ushort;
267 	value.dscale = val.read!ushort;
268 	
269 	auto len = val.length / NumericDigit.sizeof;
270 	value.digits = new NumericDigit[len];
271 	foreach(i; 0 .. len)
272 	{
273 		NumericDigit d = val.read!NumericDigit;
274 		value.digits[i] = d;
275 	}
276 	
277 	return PGNumeric(numeric_out(value));
278 }
279 
280 version(IntegrationTest2)
281 {
282     import pgator.db.pool;
283     import std.random;
284     import std.range;
285     import std.math;
286     import vibe.data.bson;
287     import derelict.pq.pq;
288     import dlogg.log;
289     import dlogg.buffered;
290     
291     void test(PQType type)(shared ILogger strictLogger, shared IConnectionPool pool)
292         if(type == PQType.Numeric)
293     {
294 		auto delayed = new shared BufferedLogger(strictLogger);
295 		scope(exit) delayed.finalize();
296 		scope(failure) delayed.minOutputLevel = LoggingLevel.Notice;
297 		
298         void testValue(shared ILogger logger, string val)
299         {
300             string query;
301             if(val == "NaN") 
302             {
303                 query = "SELECT '"~val~"'::NUMERIC as test_field";
304             } else
305             {
306                 query = "SELECT "~val~"::NUMERIC as test_field";
307             }
308 
309             logger.logInfo(query);
310             auto res = Bson.fromJson(pool.execTransaction([query]).front.toJson);
311 
312             logger.logInfo(text(res));
313             auto node = res.get!(Bson[string])["test_field"][0];
314             if(node.type == Bson.Type.double_)
315             {
316                 auto remote = node.get!double;
317                 auto local  = val.to!double;
318                 if(!isNaN(local))
319                     assert(remote == local, remote.to!string ~ "!=" ~ val); 
320                 else
321                     assert(isNaN(remote), remote.to!string ~ " is not NaN!");
322             } else
323             {
324                 auto retval = node.get!string;
325                 assert(retval == val, retval ~ "!=" ~ val); 
326             }
327         }
328         
329         string bigNumber(size_t size)
330         {
331             auto builder = appender!string;
332             immutable digits = "0123456789";
333             foreach(i; 0..size)
334                 builder.put(digits[uniform(0, digits.length)]);
335             return builder.data.strip('0');    
336         }
337         
338         strictLogger.logInfo("Testing Numeric...");
339         foreach(i; 0..100)
340         {
341             testValue(delayed, (100*uniform(-1.0, 1.0)).to!string);
342         }
343         // big numbers
344         foreach(i; 0..100)
345         {
346             testValue(delayed, bigNumber(100) ~ "." ~ bigNumber(100));
347         }
348         // special cases
349         testValue(delayed, "0");
350         testValue(delayed, "0.0146328");
351         testValue(delayed, "42");
352         testValue(delayed, "NaN");
353         testValue(delayed, "0.0007");
354         testValue(delayed, "0.007");
355         testValue(delayed, "0.07");
356         testValue(delayed, "0.7");
357         testValue(delayed, "7");
358         testValue(delayed, "70");
359         testValue(delayed, "700");
360         testValue(delayed, "7000");
361         testValue(delayed, "70000");
362         
363         testValue(delayed, "7.0");
364         testValue(delayed, "70.0");
365         testValue(delayed, "700.0");
366         testValue(delayed, "7000.0");
367         testValue(delayed, "70000.000");
368         
369         testValue(delayed, "2354877787627192443");
370         testValue(delayed, "2354877787627192443.0");
371         testValue(delayed, "2354877787627192443.00000");
372     }
373 }