1 /++ 2 Module to handle PostgreSQL array types 3 +/ 4 module dpq2.conv.arrays; 5 6 import dpq2.oids : OidType; 7 import dpq2.value; 8 9 import std.traits : isArray; 10 import std.typecons : Nullable; 11 12 @safe: 13 14 template isArrayType(T) 15 { 16 import dpq2.conv.geometric : isValidPolygon; 17 import std.range : ElementType; 18 import std.traits : Unqual; 19 20 enum isArrayType = isArray!T && !isValidPolygon!T && !is(Unqual!(ElementType!T) == ubyte) && !is(T : string); 21 } 22 23 static assert(isArrayType!(int[])); 24 static assert(!isArrayType!(ubyte[])); 25 static assert(!isArrayType!(string)); 26 27 /// Converts dynamic or static array of supported types to the coresponding PG array type value 28 Value toValue(T)(auto ref T v) 29 if (isArrayType!T) 30 { 31 import dpq2.oids : detectOidTypeFromNative, oidConvTo; 32 import std.array : Appender; 33 import std.bitmanip : nativeToBigEndian; 34 import std.exception : enforce; 35 import std.format : format; 36 import std.traits : isStaticArray; 37 38 static void writeItem(R, T)(ref R output, T item) 39 { 40 static if (is(T == ArrayElementType!T)) 41 { 42 import dpq2.conv.from_d_types : toValue; 43 44 static immutable ubyte[] nullVal = [255,255,255,255]; //special length value to indicate null value in array 45 auto v = item.toValue; // TODO: Direct serialization to buffer would be more effective 46 if (v.isNull) output ~= nullVal; 47 else 48 { 49 auto l = v._data.length; 50 enforce(l < uint.max, format!"Array item can't be larger than %s"(uint.max-1)); // -1 because uint.max is a null special value 51 output ~= (cast(uint)l).nativeToBigEndian[]; // write item length 52 output ~= v._data; 53 } 54 } 55 else 56 { 57 foreach (i; item) 58 writeItem(output, i); 59 } 60 } 61 62 alias ET = ArrayElementType!T; 63 enum dimensions = arrayDimensions!T; 64 enum elemOid = detectOidTypeFromNative!ET; 65 auto arrOid = oidConvTo!("array")(elemOid); //TODO: check in CT for supported array types 66 67 // check for null value 68 static if (!isStaticArray!T) 69 { 70 if (v is null) return Value(ValueFormat.BINARY, arrOid); 71 } 72 73 // check for null element 74 static if (__traits(compiles, v[0] is null) || is(ET == Nullable!R,R)) 75 { 76 bool hasNull = false; 77 foreach (vv; v) 78 { 79 static if (is(ET == Nullable!R,R)) hasNull = vv.isNull; 80 else hasNull = vv is null; 81 82 if (hasNull) break; 83 } 84 } 85 else bool hasNull = false; 86 87 auto buffer = Appender!(immutable(ubyte)[])(); 88 89 // write header 90 buffer ~= dimensions.nativeToBigEndian[]; // write number of dimensions 91 buffer ~= (hasNull ? 1 : 0).nativeToBigEndian[]; // write null element flag 92 buffer ~= (cast(int)elemOid).nativeToBigEndian[]; // write elements Oid 93 size_t[dimensions] dlen; 94 static foreach (d; 0..dimensions) 95 { 96 dlen[d] = getDimensionLength!d(v); 97 enforce(dlen[d] < uint.max, format!"Array length can't be larger than %s"(uint.max)); 98 buffer ~= (cast(uint)dlen[d]).nativeToBigEndian[]; // write number of dimensions 99 buffer ~= 1.nativeToBigEndian[]; // write left bound index (PG indexes from 1 implicitly) 100 } 101 102 //write data 103 foreach (i; v) writeItem(buffer, i); 104 105 return Value(buffer.data, arrOid); 106 } 107 108 @system unittest 109 { 110 import dpq2.conv.to_d_types : as; 111 import dpq2.result : asArray; 112 113 { 114 int[3][2][1] arr = [[[1,2,3], [4,5,6]]]; 115 116 assert(arr[0][0][2] == 3); 117 assert(arr[0][1][2] == 6); 118 119 auto v = arr.toValue(); 120 assert(v.oidType == OidType.Int4Array); 121 122 auto varr = v.asArray; 123 assert(varr.length == 6); 124 assert(varr.getValue(0,0,2).as!int == 3); 125 assert(varr.getValue(0,1,2).as!int == 6); 126 } 127 128 { 129 int[][][] arr = [[[1,2,3], [4,5,6]]]; 130 131 assert(arr[0][0][2] == 3); 132 assert(arr[0][1][2] == 6); 133 134 auto v = arr.toValue(); 135 assert(v.oidType == OidType.Int4Array); 136 137 auto varr = v.asArray; 138 assert(varr.length == 6); 139 assert(varr.getValue(0,0,2).as!int == 3); 140 assert(varr.getValue(0,1,2).as!int == 6); 141 } 142 143 { 144 string[] arr = ["foo", "bar", "baz"]; 145 146 auto v = arr.toValue(); 147 assert(v.oidType == OidType.TextArray); 148 149 auto varr = v.asArray; 150 assert(varr.length == 3); 151 assert(varr[0].as!string == "foo"); 152 assert(varr[1].as!string == "bar"); 153 assert(varr[2].as!string == "baz"); 154 } 155 156 { 157 string[] arr = ["foo", null, "baz"]; 158 159 auto v = arr.toValue(); 160 assert(v.oidType == OidType.TextArray); 161 162 auto varr = v.asArray; 163 assert(varr.length == 3); 164 assert(varr[0].as!string == "foo"); 165 assert(varr[1].as!string == ""); 166 assert(varr[2].as!string == "baz"); 167 } 168 169 { 170 Nullable!string[] arr = [Nullable!string("foo"), Nullable!string.init, Nullable!string("baz")]; 171 172 auto v = arr.toValue(); 173 assert(v.oidType == OidType.TextArray); 174 175 auto varr = v.asArray; 176 assert(varr.length == 3); 177 assert(varr[0].as!string == "foo"); 178 assert(varr[1].isNull); 179 assert(varr[2].as!string == "baz"); 180 } 181 } 182 183 package: 184 185 template ArrayElementType(T) 186 { 187 import std.range : ElementType; 188 import std.traits : isArray, isSomeString; 189 190 static if (!isArrayType!T) alias ArrayElementType = T; 191 else alias ArrayElementType = ArrayElementType!(ElementType!T); 192 } 193 194 unittest 195 { 196 static assert(is(ArrayElementType!(int[][][]) == int)); 197 static assert(is(ArrayElementType!(int[]) == int)); 198 static assert(is(ArrayElementType!(int) == int)); 199 static assert(is(ArrayElementType!(string[][][]) == string)); 200 static assert(is(ArrayElementType!(bool[]) == bool)); 201 } 202 203 template arrayDimensions(T) 204 if (isArray!T) 205 { 206 import std.range : ElementType; 207 208 static if (is(ElementType!T == ArrayElementType!T)) enum int arrayDimensions = 1; 209 else enum int arrayDimensions = 1 + arrayDimensions!(ElementType!T); 210 } 211 212 unittest 213 { 214 static assert(arrayDimensions!(bool[]) == 1); 215 static assert(arrayDimensions!(int[][]) == 2); 216 static assert(arrayDimensions!(int[][][]) == 3); 217 static assert(arrayDimensions!(int[][][][]) == 4); 218 } 219 220 auto getDimensionLength(int idx, T)(T v) 221 { 222 import std.range : ElementType; 223 import std.traits : isStaticArray; 224 225 static assert(idx >= 0 || !is(T == ArrayElementType!T), "Dimension index out of bounds"); 226 227 static if (idx == 0) return v.length; 228 else 229 { 230 // check same lengths on next dimension 231 static if (!isStaticArray!(ElementType!T)) 232 { 233 import std.algorithm : map, max, min, reduce; 234 import std.exception : enforce; 235 236 auto lengths = v.map!(a => a.length).reduce!(min, max); 237 enforce(lengths[0] == lengths[1], "Different lengths of sub arrays"); 238 } 239 240 return getDimensionLength!(idx-1)(v[0]); 241 } 242 } 243 244 unittest 245 { 246 { 247 int[3][2][1] arr = [[[1,2,3], [4,5,6]]]; 248 assert(getDimensionLength!0(arr) == 1); 249 assert(getDimensionLength!1(arr) == 2); 250 assert(getDimensionLength!2(arr) == 3); 251 } 252 253 { 254 int[][][] arr = [[[1,2,3], [4,5,6]]]; 255 assert(getDimensionLength!0(arr) == 1); 256 assert(getDimensionLength!1(arr) == 2); 257 assert(getDimensionLength!2(arr) == 3); 258 } 259 260 { 261 import std.exception : assertThrown; 262 int[][] arr = [[1,2,3], [4,5]]; 263 assert(getDimensionLength!0(arr) == 2); 264 assertThrown(getDimensionLength!1(arr) == 3); 265 } 266 }