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 element 68 static if (__traits(compiles, v[0] is null) || is(ET == Nullable!R,R)) 69 { 70 bool hasNull = false; 71 foreach (vv; v) 72 { 73 static if (is(ET == Nullable!R,R)) hasNull = vv.isNull; 74 else hasNull = vv is null; 75 76 if (hasNull) break; 77 } 78 } 79 else bool hasNull = false; 80 81 auto buffer = Appender!(immutable(ubyte)[])(); 82 83 // write header 84 buffer ~= dimensions.nativeToBigEndian[]; // write number of dimensions 85 buffer ~= (hasNull ? 1 : 0).nativeToBigEndian[]; // write null element flag 86 buffer ~= (cast(int)elemOid).nativeToBigEndian[]; // write elements Oid 87 size_t[dimensions] dlen; 88 static foreach (d; 0..dimensions) 89 { 90 dlen[d] = getDimensionLength!d(v); 91 enforce(dlen[d] < uint.max, format!"Array length can't be larger than %s"(uint.max)); 92 buffer ~= (cast(uint)dlen[d]).nativeToBigEndian[]; // write number of dimensions 93 buffer ~= 1.nativeToBigEndian[]; // write left bound index (PG indexes from 1 implicitly) 94 } 95 96 //write data 97 foreach (i; v) writeItem(buffer, i); 98 99 return Value(buffer.data, arrOid); 100 } 101 102 @system unittest 103 { 104 import dpq2.conv.to_d_types : as; 105 import dpq2.result : asArray; 106 107 { 108 int[3][2][1] arr = [[[1,2,3], [4,5,6]]]; 109 110 assert(arr[0][0][2] == 3); 111 assert(arr[0][1][2] == 6); 112 113 auto v = arr.toValue(); 114 assert(v.oidType == OidType.Int4Array); 115 116 auto varr = v.asArray; 117 assert(varr.length == 6); 118 assert(varr.getValue(0,0,2).as!int == 3); 119 assert(varr.getValue(0,1,2).as!int == 6); 120 } 121 122 { 123 int[][][] arr = [[[1,2,3], [4,5,6]]]; 124 125 assert(arr[0][0][2] == 3); 126 assert(arr[0][1][2] == 6); 127 128 auto v = arr.toValue(); 129 assert(v.oidType == OidType.Int4Array); 130 131 auto varr = v.asArray; 132 assert(varr.length == 6); 133 assert(varr.getValue(0,0,2).as!int == 3); 134 assert(varr.getValue(0,1,2).as!int == 6); 135 } 136 137 { 138 string[] arr = ["foo", "bar", "baz"]; 139 140 auto v = arr.toValue(); 141 assert(v.oidType == OidType.TextArray); 142 143 auto varr = v.asArray; 144 assert(varr.length == 3); 145 assert(varr[0].as!string == "foo"); 146 assert(varr[1].as!string == "bar"); 147 assert(varr[2].as!string == "baz"); 148 } 149 150 { 151 string[] arr = ["foo", null, "baz"]; 152 153 auto v = arr.toValue(); 154 assert(v.oidType == OidType.TextArray); 155 156 auto varr = v.asArray; 157 assert(varr.length == 3); 158 assert(varr[0].as!string == "foo"); 159 assert(varr[1].as!string == ""); 160 assert(varr[2].as!string == "baz"); 161 } 162 163 { 164 string[] arr; 165 166 auto v = arr.toValue(); 167 assert(v.oidType == OidType.TextArray); 168 assert(!v.isNull); 169 170 auto varr = v.asArray; 171 assert(varr.length == 0); 172 } 173 174 { 175 Nullable!string[] arr = [Nullable!string("foo"), Nullable!string.init, Nullable!string("baz")]; 176 177 auto v = arr.toValue(); 178 assert(v.oidType == OidType.TextArray); 179 180 auto varr = v.asArray; 181 assert(varr.length == 3); 182 assert(varr[0].as!string == "foo"); 183 assert(varr[1].isNull); 184 assert(varr[2].as!string == "baz"); 185 } 186 } 187 188 package: 189 190 template ArrayElementType(T) 191 { 192 import std.range : ElementType; 193 import std.traits : isArray, isSomeString; 194 195 static if (!isArrayType!T) alias ArrayElementType = T; 196 else alias ArrayElementType = ArrayElementType!(ElementType!T); 197 } 198 199 unittest 200 { 201 static assert(is(ArrayElementType!(int[][][]) == int)); 202 static assert(is(ArrayElementType!(int[]) == int)); 203 static assert(is(ArrayElementType!(int) == int)); 204 static assert(is(ArrayElementType!(string[][][]) == string)); 205 static assert(is(ArrayElementType!(bool[]) == bool)); 206 } 207 208 template arrayDimensions(T) 209 if (isArray!T) 210 { 211 import std.range : ElementType; 212 213 static if (is(ElementType!T == ArrayElementType!T)) enum int arrayDimensions = 1; 214 else enum int arrayDimensions = 1 + arrayDimensions!(ElementType!T); 215 } 216 217 unittest 218 { 219 static assert(arrayDimensions!(bool[]) == 1); 220 static assert(arrayDimensions!(int[][]) == 2); 221 static assert(arrayDimensions!(int[][][]) == 3); 222 static assert(arrayDimensions!(int[][][][]) == 4); 223 } 224 225 auto getDimensionLength(int idx, T)(T v) 226 { 227 import std.range : ElementType; 228 import std.traits : isStaticArray; 229 230 static assert(idx >= 0 || !is(T == ArrayElementType!T), "Dimension index out of bounds"); 231 232 static if (idx == 0) return v.length; 233 else 234 { 235 // check same lengths on next dimension 236 static if (!isStaticArray!(ElementType!T)) 237 { 238 import std.algorithm : map, max, min, reduce; 239 import std.exception : enforce; 240 241 auto lengths = v.map!(a => a.length).reduce!(min, max); 242 enforce(lengths[0] == lengths[1], "Different lengths of sub arrays"); 243 } 244 245 return getDimensionLength!(idx-1)(v[0]); 246 } 247 } 248 249 unittest 250 { 251 { 252 int[3][2][1] arr = [[[1,2,3], [4,5,6]]]; 253 assert(getDimensionLength!0(arr) == 1); 254 assert(getDimensionLength!1(arr) == 2); 255 assert(getDimensionLength!2(arr) == 3); 256 } 257 258 { 259 int[][][] arr = [[[1,2,3], [4,5,6]]]; 260 assert(getDimensionLength!0(arr) == 1); 261 assert(getDimensionLength!1(arr) == 2); 262 assert(getDimensionLength!2(arr) == 3); 263 } 264 265 { 266 import std.exception : assertThrown; 267 int[][] arr = [[1,2,3], [4,5]]; 268 assert(getDimensionLength!0(arr) == 2); 269 assertThrown(getDimensionLength!1(arr) == 3); 270 } 271 }