1 module dpq2.conv.to_d_types; 2 3 @safe: 4 5 import dpq2; 6 7 import dpq2.conv.numeric: rawValueToNumeric; 8 import dpq2.conv.time: binaryValueAs, TimeStampWithoutTZ; 9 10 import vibe.data.json: Json, parseJsonString; 11 import vibe.data.bson: Bson; 12 import std.traits; 13 import std.uuid; 14 import std.datetime; 15 import std.traits: isScalarType; 16 17 // Supported PostgreSQL binary types 18 alias PGboolean = bool; /// boolean 19 alias PGsmallint = short; /// smallint 20 alias PGinteger = int; /// integer 21 alias PGbigint = long; /// bigint 22 alias PGreal = float; /// real 23 alias PGdouble_precision = double; /// double precision 24 alias PGtext = string; /// text 25 alias PGnumeric = string; /// numeric represented as string 26 alias PGbytea = const ubyte[]; /// bytea 27 alias PGuuid = UUID; /// UUID 28 alias PGdate = Date; /// Date (no time of day) 29 alias PGtime_without_time_zone = TimeOfDay; /// Time of day (no date) 30 alias PGtimestamp_without_time_zone = TimeStampWithoutTZ; /// Both date and time (no time zone) 31 alias PGjson = Json; /// json or jsonb 32 33 package void throwTypeComplaint(OidType receivedType, string expectedType, string file, size_t line) pure 34 { 35 throw new AnswerConvException( 36 ConvExceptionType.NOT_IMPLEMENTED, 37 "Format of the column ("~to!string(receivedType)~") doesn't match to D native "~expectedType, 38 file, line 39 ); 40 } 41 42 private alias VF = ValueFormat; 43 private alias AE = AnswerConvException; 44 private alias ET = ConvExceptionType; 45 46 /// Returns cell value as native string type from text or binary formatted field 47 @property string as(T)(in Value v) pure @trusted 48 if(is(T == string)) 49 { 50 if(v.format == VF.BINARY) 51 { 52 if(!(v.oidType == OidType.Text || v.oidType == OidType.Numeric || v.oidType == OidType.Json)) 53 throwTypeComplaint(v.oidType, "string, numeric or json", __FILE__, __LINE__); 54 55 if(v.oidType == OidType.Numeric) 56 return rawValueToNumeric(v.data); 57 } 58 59 return valueAsString(v); 60 } 61 62 /// Returns value as D type value from binary formatted field 63 @property T as(T)(in Value v) 64 if(!is(T == string) && !is(T == Bson)) 65 { 66 if(!(v.format == VF.BINARY)) 67 throw new AE(ET.NOT_BINARY, 68 msg_NOT_BINARY, __FILE__, __LINE__); 69 70 return binaryValueAs!T(v); 71 } 72 73 package: 74 75 @property string valueAsString(in Value v) pure 76 { 77 return (cast(const(char[])) v.data).to!string; 78 } 79 80 /// Returns value as bytes from binary formatted field 81 @property T binaryValueAs(T)(in Value v) 82 if( is( T == const(ubyte[]) ) ) 83 { 84 if(!(v.oidType == OidType.ByteArray)) 85 throwTypeComplaint(v.oidType, "ubyte[] or string", __FILE__, __LINE__); 86 87 return v.data; 88 } 89 90 /// Returns cell value as native integer or decimal values 91 /// 92 /// Postgres type "numeric" is oversized and not supported by now 93 @property T binaryValueAs(T)(in Value v) 94 if( isNumeric!(T) ) 95 { 96 static if(isIntegral!(T)) 97 if(!isNativeInteger(v.oidType)) 98 throwTypeComplaint(v.oidType, "integral types", __FILE__, __LINE__); 99 100 static if(isFloatingPoint!(T)) 101 if(!isNativeFloat(v.oidType)) 102 throwTypeComplaint(v.oidType, "floating point types", __FILE__, __LINE__); 103 104 if(!(v.data.length == T.sizeof)) 105 throw new AE(ET.SIZE_MISMATCH, 106 to!string(v.oidType)~" length ("~to!string(v.data.length)~") isn't equal to native D type "~ 107 to!string(typeid(T))~" size ("~to!string(T.sizeof)~")", 108 __FILE__, __LINE__); 109 110 ubyte[T.sizeof] s = v.data[0..T.sizeof]; 111 return bigEndianToNative!(T)(s); 112 } 113 114 /// Returns UUID as native UUID value 115 @property UUID binaryValueAs(T)(in Value v) 116 if( is( T == UUID ) ) 117 { 118 if(!(v.oidType == OidType.UUID)) 119 throwTypeComplaint(v.oidType, "UUID", __FILE__, __LINE__); 120 121 if(!(v.data.length == 16)) 122 throw new AE(ET.SIZE_MISMATCH, 123 "Value length isn't equal to Postgres UUID size", __FILE__, __LINE__); 124 125 UUID r; 126 r.data = v.data; 127 return r; 128 } 129 130 /// Returns boolean as native bool value 131 @property bool binaryValueAs(T)(in Value v) 132 if( is( T == bool ) ) 133 { 134 if(!(v.oidType == OidType.Bool)) 135 throwTypeComplaint(v.oidType, "bool", __FILE__, __LINE__); 136 137 if(!(v.data.length == 1)) 138 throw new AE(ET.SIZE_MISMATCH, 139 "Value length isn't equal to Postgres boolean size", __FILE__, __LINE__); 140 141 return v.data[0] != 0; 142 } 143 144 /// Returns Vibe.d's Json 145 @property Json binaryValueAs(T)(in Value v) @trusted 146 if( is( T == Json ) ) 147 { 148 Json res; 149 150 switch(v.oidType) 151 { 152 case OidType.Json: 153 // represent value as text and parse it into Json 154 string t = v.valueAsString; 155 res = parseJsonString(t); 156 break; 157 158 case OidType.Jsonb: 159 assert(false, "Is not implemented"); 160 //break; 161 162 default: 163 throwTypeComplaint(v.oidType, "json or jsonb", __FILE__, __LINE__); 164 } 165 166 return res; 167 } 168 169 public void _integration_test( string connParam ) @system 170 { 171 auto conn = new Connection(connParam); 172 173 QueryParams params; 174 params.resultFormat = ValueFormat.BINARY; 175 176 { 177 void testIt(T)(T nativeValue, string pgType, string pgValue) 178 { 179 params.sqlCommand = "SELECT "~pgValue~"::"~pgType~" as d_type_test_value"; 180 auto answer = conn.execParams(params); 181 immutable Value v = answer[0][0]; 182 auto result = v.as!T; 183 184 assert(result == nativeValue, "Received unexpected value\nreceived pgType="~to!string(v.oidType)~"\nexpected nativeType="~to!string(typeid(T))~ 185 "\nsent pgValue="~pgValue~"\nexpected nativeValue="~to!string(nativeValue)~"\nresult="~to!string(result)); 186 } 187 188 alias C = testIt; // "C" means "case" 189 190 C!PGboolean(true, "boolean", "true"); 191 C!PGboolean(false, "boolean", "false"); 192 C!PGsmallint(-32_761, "smallint", "-32761"); 193 C!PGinteger(-2_147_483_646, "integer", "-2147483646"); 194 C!PGbigint(-9_223_372_036_854_775_806, "bigint", "-9223372036854775806"); 195 C!PGreal(-12.3456f, "real", "-12.3456"); 196 C!PGdouble_precision(-1234.56789012345, "double precision", "-1234.56789012345"); 197 C!PGtext("first line\nsecond line", "text", "'first line\nsecond line'"); 198 C!PGbytea([0x44, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x00, 0x21], 199 "bytea", r"E'\\x44 20 72 75 6c 65 73 00 21'"); // "D rules\x00!" (ASCII) 200 C!PGuuid(UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640"), "uuid", "'8b9ab33a-96e9-499b-9c36-aad1fe86d640'"); 201 202 // numeric testing 203 C!PGnumeric("NaN", "numeric", "'NaN'"); 204 205 const string[] numericTests = [ 206 "42", 207 "-42", 208 "0", 209 "0.0146328", 210 "0.0007", 211 "0.007", 212 "0.07", 213 "0.7", 214 "7", 215 "70", 216 "700", 217 "7000", 218 "70000", 219 220 "7.0", 221 "70.0", 222 "700.0", 223 "7000.0", 224 "70000.000", 225 226 "2354877787627192443", 227 "2354877787627192443.0", 228 "2354877787627192443.00000", 229 "-2354877787627192443.00000" 230 ]; 231 232 foreach(i, s; numericTests) 233 C!PGnumeric(s, "numeric", s); 234 235 // date and time testing 236 C!PGdate(Date(2016, 01, 8), "date", "'January 8, 2016'"); 237 C!PGtime_without_time_zone(TimeOfDay(12, 34, 56), "time without time zone", "'12:34:56'"); 238 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ(DateTime(1997, 12, 17, 7, 37, 16), FracSec.from!"usecs"(12)), "timestamp without time zone", "'1997-12-17 07:37:16.000012'"); 239 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ.max, "timestamp without time zone", "'infinity'"); 240 C!PGtimestamp_without_time_zone(TimeStampWithoutTZ.min, "timestamp without time zone", "'-infinity'"); 241 242 // json 243 C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", "'{\"float_value\": 123.456,\"text_str\": \"text string\"}'"); 244 245 // json as string 246 C!string("{\"float_value\": 123.456}", "json", "'{\"float_value\": 123.456}'"); 247 248 // jsonb 249 //C!PGjson(Json(["integer": Json(123), "float": Json(123.456), "text_string": Json("This is a text string")]), "jsonb", 250 //"'{\"integer\": 123, \"float\": 123.456,\"text_string\": \"This is a text string\"}'"); 251 } 252 }