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 }