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