1 /*
2 *   PostgreSQL numeric format
3 *
4 *   Copyright: © 2014 DSoftOut
5 *   Authors: NCrashed <ncrashed@gmail.com>
6 */
7 module dpq2.conv.numeric;
8 
9 private pure // inner representation from libpq sources
10 {
11     alias NumericDigit = ushort;
12     enum DEC_DIGITS = 4;
13     enum NUMERIC_NEG       = 0x4000;
14     enum NUMERIC_NAN       = 0xC000;
15 
16     struct NumericVar
17     {
18         int weight;
19         int sign;
20         int dscale;
21         NumericDigit[] digits;
22     }
23 
24     string numeric_out(in NumericVar num)
25     {
26         string str;
27 
28         if(num.sign == NUMERIC_NAN)
29         {
30 	    return "NaN";
31         }
32 
33         str = get_str_from_var(num);
34 
35         return str;
36     }
37 
38     /*
39      * get_str_from_var() -
40      *
41      *  Convert a var to text representation (guts of numeric_out).
42      *  The var is displayed to the number of digits indicated by its dscale.
43      *  Returns a palloc'd string.
44      */
45     string get_str_from_var(in NumericVar var)
46     {
47 	int          dscale;
48 	ubyte[]      str;
49 	ubyte*       cp;
50 	ubyte*       endcp;
51 	int          i;
52 	int          d;
53 	NumericDigit dig;
54 
55 	static if(DEC_DIGITS > 1)
56 	{
57 	    NumericDigit d1;
58 	}
59 
60 	dscale = var.dscale;
61 
62 	/*
63 	 * Allocate space for the result.
64 	 *
65 	 * i is set to the # of decimal digits before decimal point. dscale is the
66 	 * # of decimal digits we will print after decimal point. We may generate
67 	 * as many as DEC_DIGITS-1 excess digits at the end, and in addition we
68 	 * need room for sign, decimal point, null terminator.
69 	 */
70 	i = (var.weight + 1) * DEC_DIGITS;
71 	if (i <= 0)
72 	    i = 1;
73 
74 	str = new ubyte[i + dscale + DEC_DIGITS + 2];
75 	cp = str.ptr;
76 
77 	/*
78 	 * Output a dash for negative values
79 	 */
80 	if (var.sign == NUMERIC_NEG)
81 	    *cp++ = '-';
82 
83 	/*
84 	 * Output all digits before the decimal point
85 	 */
86 	if (var.weight < 0)
87 	{
88 	    d = var.weight + 1;
89 	    *cp++ = '0';
90 	}
91 	else
92 	{
93 	    for (d = 0; d <= var.weight; d++)
94 	    {
95 		dig = (d < var.digits.length) ? var.digits[d] : 0;
96 		/* In the first digit, suppress extra leading decimal zeroes */
97 		static if(DEC_DIGITS == 4)
98 		{
99 		    bool putit = (d > 0);
100 
101 		    d1 = dig / 1000;
102 		    dig -= d1 * 1000;
103 		    putit |= (d1 > 0);
104 		    if (putit)
105 			*cp++ = cast(char)(d1 + '0');
106 		    d1 = dig / 100;
107 		    dig -= d1 * 100;
108 		    putit |= (d1 > 0);
109 		    if (putit)
110 			*cp++ = cast(char)(d1 + '0');
111 		    d1 = dig / 10;
112 		    dig -= d1 * 10;
113 		    putit |= (d1 > 0);
114 		    if (putit)
115 			*cp++ = cast(char)(d1 + '0');
116 		    *cp++ = cast(char)(dig + '0');
117 		}
118 		else static if(DEC_DIGITS == 2)
119 		{
120 			d1 = dig / 10;
121 			dig -= d1 * 10;
122 			if (d1 > 0 || d > 0)
123 			    *cp++ = cast(char)(d1 + '0');
124 			*cp++ = cast(char)(dig + '0');
125 		}
126 		else static if(DEC_DIGITS == 1)
127 		{
128 		    *cp++ = cast(char)(dig + '0');
129 		}
130 		else pragma(error, "unsupported NBASE");
131 	    }
132 	}
133 
134 	/*
135 	 * If requested, output a decimal point and all the digits that follow it.
136 	 * We initially put out a multiple of DEC_DIGITS digits, then truncate if
137 	 * needed.
138 	 */
139 	if (dscale > 0)
140 	{
141 	    *cp++ = '.';
142 	    endcp = cp + dscale;
143 	    for (i = 0; i < dscale; d++, i += DEC_DIGITS)
144 	    {
145 		dig = (d >= 0 && d < var.digits.length) ? var.digits[d] : 0;
146 		static if(DEC_DIGITS == 4)
147 		{
148 			d1 = dig / 1000;
149 			dig -= d1 * 1000;
150 			*cp++ = cast(char)(d1 + '0');
151 			d1 = dig / 100;
152 			dig -= d1 * 100;
153 			*cp++ = cast(char)(d1 + '0');
154 			d1 = dig / 10;
155 			dig -= d1 * 10;
156 			*cp++ = cast(char)(d1 + '0');
157 			*cp++ = cast(char)(dig + '0');
158 		}
159 		else static if(DEC_DIGITS == 2)
160 		{
161 			d1 = dig / 10;
162 			dig -= d1 * 10;
163 			*cp++ = cast(char)(d1 + '0');
164 			*cp++ = cast(char)(dig + '0');
165 		}
166 		else static if(DEC_DIGITS == 1)
167 		{
168 		    *cp++ = cast(char)(dig + '0');
169 	    }
170 	    else pragma(error, "unsupported NBASE");
171 	    }
172 	    cp = endcp;
173 	}
174 
175 	/*
176 	 * terminate the string and return it
177 	 */
178 	*cp = '\0';
179 
180 	return (cast(char*) str).fromStringz;
181     }
182 }
183 
184 import std.conv: to;
185 import std.string: fromStringz;
186 import std.bitmanip: bigEndianToNative;
187 
188 package string rawValueToNumeric(in ubyte[] v) pure
189 {
190     import dpq2.result: ValueConvException, ConvExceptionType;
191 
192     struct NumericVar_net // network byte order
193     {
194 	ubyte[2] num; // num of digits
195         ubyte[2] weight;
196         ubyte[2] sign;
197         ubyte[2] dscale;
198     }
199 
200     if(!(v.length >= NumericVar_net.sizeof))
201         throw new ValueConvException(ConvExceptionType.SIZE_MISMATCH,
202             "Value length ("~to!string(v.length)~") less than it is possible for numeric type",
203             __FILE__, __LINE__);
204 
205     NumericVar_net* h = cast(NumericVar_net*) v.ptr;
206 
207     NumericVar res;
208     res.weight = bigEndianToNative!short(h.weight);
209     res.sign   = bigEndianToNative!ushort(h.sign);
210     res.dscale = bigEndianToNative!ushort(h.dscale);
211 
212     auto len = (v.length - NumericVar_net.sizeof) / NumericDigit.sizeof;
213 
214     res.digits = new NumericDigit[len];
215 
216     size_t offset = NumericVar_net.sizeof;
217     foreach(i; 0 .. len)
218     {
219 	res.digits[i] = bigEndianToNative!NumericDigit(
220 		(&(v[offset]))[0..NumericDigit.sizeof]
221 	    );
222 	offset += NumericDigit.sizeof;
223     }
224 
225     return numeric_out(res);
226 }