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