From 60631409cb5f27e94bde65be6b8645e67e364e3e Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Fri, 31 Jan 2025 12:18:05 +0300 Subject: [PATCH 1/4] inet and cidr types support added --- dub.json | 1 + src/dpq2/conv/from_d_types.d | 1 + src/dpq2/conv/inet.d | 291 +++++++++++++++++++++++++++++++++++ src/dpq2/conv/native_tests.d | 26 ++++ src/dpq2/conv/to_d_types.d | 1 + src/dpq2/conv/to_variant.d | 7 + src/dpq2/oids.d | 11 +- src/dpq2/value.d | 7 +- 8 files changed, 342 insertions(+), 3 deletions(-) create mode 100644 src/dpq2/conv/inet.d diff --git a/dub.json b/dub.json index ce2b9343..2115d893 100644 --- a/dub.json +++ b/dub.json @@ -48,6 +48,7 @@ { "dpq2": { "version": "*", "dflags-dmd": ["-preview=in"] }, "vibe-serialization": { "version": "*", "dflags-dmd": ["-preview=in"] }, + "vibe-core": { "version": "*", "dflags-dmd": ["-preview=in"] }, "gfm:math": "~>8.0.6" }, "configurations": [ diff --git a/src/dpq2/conv/from_d_types.d b/src/dpq2/conv/from_d_types.d index 0fc58fc4..a6f7e663 100644 --- a/src/dpq2/conv/from_d_types.d +++ b/src/dpq2/conv/from_d_types.d @@ -5,6 +5,7 @@ module dpq2.conv.from_d_types; public import dpq2.conv.arrays : isArrayType, toValue, isStaticArrayString; public import dpq2.conv.geometric : isGeometricType, toValue; +public import dpq2.conv.inet: toValue, vibe2pg; import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC, TimeOfDayWithTZ, Interval; import dpq2.oids : detectOidTypeFromNative, oidConvTo, OidType; import dpq2.value : Value, ValueFormat; diff --git a/src/dpq2/conv/inet.d b/src/dpq2/conv/inet.d new file mode 100644 index 00000000..eac7be9a --- /dev/null +++ b/src/dpq2/conv/inet.d @@ -0,0 +1,291 @@ +module dpq2.conv.inet; + +import dpq2.conv.to_d_types; +import dpq2.oids: OidType; +import dpq2.value; + +import std.bitmanip: bigEndianToNative, nativeToBigEndian; +import std.conv: to; +import std.socket; + +@safe: + +enum PgFamily : ubyte { + PGSQL_AF_INET = AddressFamily.INET, + PGSQL_AF_INET6, +} + +alias InetAddress = TInetAddress!false; +alias CidrAddress = TInetAddress!true; + +package struct TInetAddress (bool isCIDR) +{ + PgFamily family; + ubyte netmask; + AddrValue addr; + alias addr this; + + /// + this(in InternetAddress ia, ubyte mask = 32) + { + addr4 = ia.addr; + family = PgFamily.PGSQL_AF_INET; + netmask = mask; + } + + /// + this(in Internet6Address ia, ubyte mask = 128) + { + addr6 = ia.addr; + family = PgFamily.PGSQL_AF_INET6; + netmask = mask; + } + + /// + Address createStdAddr(ushort port = InternetAddress.PORT_ANY) const + { + switch(family) + { + case PgFamily.PGSQL_AF_INET: + return new InternetAddress(addr4, port); + + case PgFamily.PGSQL_AF_INET6: + return new Internet6Address(addr6, port); + + default: + assert(0, "Unsupported address family "~family.to!string); + } + } + + /// + auto toString() const + { + import std.format: format; + + switch (family) + { + case PgFamily.PGSQL_AF_INET: + case PgFamily.PGSQL_AF_INET6: + return format("%s/%d", createStdAddr.toAddrString, this.netmask); + + default: + return format("Unsupported address family %s", family.to!string); //TODO: deduplicate this code + } + } +} + +unittest +{ + auto std_addr = new InternetAddress("127.0.0.1", 123); + auto pg_addr = InetAddress(std_addr); + + assert(pg_addr.createStdAddr.toAddrString == "127.0.0.1"); + + auto v = pg_addr.toValue; + assert(v.binaryValueAs!InetAddress.toString == "127.0.0.1/32"); + assert(v.binaryValueAs!InetAddress.createStdAddr.toAddrString == "127.0.0.1"); + assert(v.binaryValueAs!InetAddress == pg_addr); +} + +unittest +{ + auto std_addr = new Internet6Address("::1", 123); + auto pg_addr = InetAddress(std_addr); + + assert(pg_addr.createStdAddr.toAddrString == "::1"); + + auto v = pg_addr.toValue; + assert(v.binaryValueAs!InetAddress.toString == "::1/128"); + assert(v.binaryValueAs!InetAddress.createStdAddr.toAddrString == "::1"); + assert(v.binaryValueAs!InetAddress == pg_addr); +} + +/// +InetAddress vibe2pg(VibeNetworkAddress)(VibeNetworkAddress a) +{ + InetAddress r; + + switch(a.family) + { + case AddressFamily.INET: + r.family = PgFamily.PGSQL_AF_INET; + r.netmask = 32; + r.addr4 = a.sockAddrInet4.sin_addr.s_addr.representAsBytes.bigEndianToNative!uint; + break; + + case AddressFamily.INET6: + r.family = PgFamily.PGSQL_AF_INET6; + r.netmask = 128; + r.addr6 = AddrValue(a.sockAddrInet6.sin6_addr.s6_addr).swapEndiannesForBigEndianSystems; + break; + + default: + throw new ValueConvException( + ConvExceptionType.NOT_IMPLEMENTED, + "Unsupported address family: "~a.family.to!string + ); + } + + return r; +} + +private ref ubyte[T.sizeof] representAsBytes(T)(const ref return T s) @trusted +{ + return *cast(ubyte[T.sizeof]*) &s; +} + +private union Hdr +{ + ubyte[4] bytes; + + struct + { + PgFamily family; + ubyte netmask; + ubyte always_zero; + ubyte addr_len; + } +} + +/// Constructs Value from InetAddress or from CidrAddress +Value toValue(T)(T v) +if(is(T == InetAddress) || is(T == CidrAddress)) +{ + Hdr hdr; + hdr.family = v.family; + + ubyte[] addr_net_byte_order; + + switch(v.family) + { + case PgFamily.PGSQL_AF_INET: + addr_net_byte_order ~= v.addr4.nativeToBigEndian; + break; + + case PgFamily.PGSQL_AF_INET6: + addr_net_byte_order ~= v.addr.swapEndiannesForBigEndianSystems; + break; + + default: + throw new ValueConvException( + ConvExceptionType.NOT_IMPLEMENTED, + "Unsupported address family: "~v.family.to!string + ); + } + + hdr.addr_len = addr_net_byte_order.length.to!ubyte; + hdr.netmask = v.netmask; + + immutable r = (hdr.bytes ~ addr_net_byte_order).idup; + return Value(r, OidType.HostAddress); +} + +package: + +/// Convert Value to network address type +T binaryValueAs(T)(in Value v) +if(is(T == InetAddress) || is(T == CidrAddress)) +{ + enum oidType = is(T == InetAddress) ? OidType.HostAddress : OidType.NetworkAddress; + enum typeName = is(T == InetAddress) ? "inet" : "cidr"; + + if(v.oidType != oidType) + throwTypeComplaint(v.oidType, typeName); + + Hdr hdr; + enum headerLen = hdr.sizeof; + enum ipv4_addr_len = 4; + + if(v.data.length < hdr.sizeof + ipv4_addr_len) + throw new ValueConvException(ConvExceptionType.SIZE_MISMATCH, "unexpected data ending"); + + hdr.bytes = v.data[0 .. hdr.bytes.length]; + + ubyte lenMustBe; + switch(hdr.family) + { + case PgFamily.PGSQL_AF_INET: lenMustBe = ipv4_addr_len; break; + case PgFamily.PGSQL_AF_INET6: lenMustBe = 16; break; + default: + throw new ValueConvException( + ConvExceptionType.NOT_IMPLEMENTED, + "Unsupported address family: "~hdr.family.to!string + ); + } + + if(hdr.addr_len != lenMustBe && hdr.always_zero == 0) + throw new ValueConvException( + ConvExceptionType.SIZE_MISMATCH, + "Wrong address length, must be "~lenMustBe.to!string + ); + + if(headerLen + hdr.addr_len != v.data.length) + throw new ValueConvException( + ConvExceptionType.SIZE_MISMATCH, + "Address length not matches to Value data length" + ); + + import std.bitmanip: bigEndianToNative; + + T r; + r.family = hdr.family; + r.netmask = hdr.netmask; + + switch(hdr.family) + { + case PgFamily.PGSQL_AF_INET: + const ubyte[4] b = v.data[headerLen..$]; + r.addr4 = b.bigEndianToNative!uint; + break; + + case PgFamily.PGSQL_AF_INET6: + AddrValue av; + av.addr6 = v.data[headerLen..$]; + r.addr6 = av.swapEndiannesForBigEndianSystems; + break; + + default: assert(0); + } + + return r; +} + +private: + +private union AddrValue +{ + ubyte[16] addr6; // IPv6 address in native byte order + short[8] addr6_parts; // for endiannes swap purpose + + struct + { + ubyte[12] __unused; + uint addr4; // IPv4 address in native byte order + } +} + +import std.system: Endian, endian; + +static if(endian == Endian.littleEndian) +auto swapEndiannesForBigEndianSystems(in AddrValue s) +{ + // do nothing for little endian + return s.addr6; +} +else +{ + +ubyte[16] swapEndiannesForBigEndianSystems(in AddrValue s) +{ + import std.bitmanip: swapEndian; + + AddrValue r; + enum len = AddrValue.addr6_parts.length; + + foreach(ubyte i; 0 .. len) + r.addr6_parts[i] = s.addr6_parts[i].swapEndian; + + return r.addr6; +} + +} diff --git a/src/dpq2/conv/native_tests.d b/src/dpq2/conv/native_tests.d index 576f8506..00bd97dc 100644 --- a/src/dpq2/conv/native_tests.d +++ b/src/dpq2/conv/native_tests.d @@ -3,8 +3,10 @@ module dpq2.conv.native_tests; import dpq2; import dpq2.conv.arrays : isArrayType; import dpq2.conv.geometric: Line; +import dpq2.conv.inet: InetAddress, CidrAddress; import std.bitmanip : BitArray; import std.datetime; +import std.socket: InternetAddress, Internet6Address; import std.typecons: Nullable; import std.uuid: UUID; import std.variant: Variant; @@ -40,6 +42,7 @@ public void _integration_test( string connParam ) @system { import std.format: format; import dpq2.connection: createTestConn; + import vibe.core.net: VibeNetworkAddress = NetworkAddress; auto conn = createTestConn(connParam); @@ -258,6 +261,28 @@ public void _integration_test( string connParam ) @system C!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ), "timestamptz", "'1997-12-17 07:37:16.000012+02'"); C!(Nullable!SysTime)(Nullable!SysTime(SysTime(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12), testTZ)), "timestamptz", "'1997-12-17 07:37:16.000012+02'"); + // inet + const testInetAddr1 = InetAddress(new InternetAddress("127.0.0.1", InternetAddress.PORT_ANY), 9); + C!InetAddress(testInetAddr1, "inet", `'127.0.0.1/9'`); + const testInetAddr2 = InetAddress(new InternetAddress("127.0.0.1", InternetAddress.PORT_ANY)); + C!InetAddress(testInetAddr2, "inet", `'127.0.0.1/32'`); + const testInetAddr3 = VibeNetworkAddress(new InternetAddress("127.0.0.1", InternetAddress.PORT_ANY)).vibe2pg; + C!InetAddress(testInetAddr3, "inet", `'127.0.0.1/32'`); + + // inet6 + const testInet6Addr1 = InetAddress(new Internet6Address("2::1", InternetAddress.PORT_ANY)); + C!InetAddress(testInet6Addr1, "inet", `'2::1/128'`); + const testInet6Addr2 = InetAddress(new Internet6Address("2001:0:130F::9C0:876A:130B", InternetAddress.PORT_ANY),24); + C!InetAddress(testInet6Addr2, "inet", `'2001:0:130f::9c0:876a:130b/24'`); + const testInet6Addr3 = VibeNetworkAddress(new Internet6Address("2001:0:130F::9C0:876A:130B", InternetAddress.PORT_ANY)).vibe2pg; + C!InetAddress(testInet6Addr3, "inet", `'2001:0:130f::9c0:876a:130b/128'`); + + // cidr + const testCidrAddr1 = CidrAddress(new InternetAddress("192.168.0.0", InternetAddress.PORT_ANY), 25); + C!CidrAddress(testCidrAddr1, "cidr", `'192.168.0.0/25'`); + const testCidrAddr2 = CidrAddress(new Internet6Address("::", InternetAddress.PORT_ANY), 64); + C!CidrAddress(testCidrAddr2, "cidr", `'::/64'`); + // json C!PGjson(Json(["float_value": Json(123.456), "text_str": Json("text string")]), "json", `'{"float_value": 123.456,"text_str": "text string"}'`); C!(Nullable!PGjson)(Nullable!Json(Json(["foo": Json("bar")])), "json", `'{"foo":"bar"}'`); @@ -289,6 +314,7 @@ public void _integration_test( string connParam ) @system C!(PGuuid[])([UUID("8b9ab33a-96e9-499b-9c36-aad1fe86d640")], "uuid[]", "'{8b9ab33a-96e9-499b-9c36-aad1fe86d640}'"); C!(PGline[])([Line(1,2,3), Line(4,5,6)], "line[]", `'{"{1,2,3}","{4,5,6}"}'`); C!(PGtimestamp[])([PGtimestamp(DateTime(1997, 12, 17, 7, 37, 16), dur!"usecs"(12))], "timestamp[]", `'{"1997-12-17 07:37:16.000012"}'`); + C!(InetAddress[])([testInetAddr1, testInet6Addr2], "inet[]", `'{127.0.0.1/9,2001:0:130f::9c0:876a:130b/24}'`); C!(Nullable!(int[]))(Nullable!(int[]).init, "int[]", "NULL"); C!(Nullable!(int[]))(Nullable!(int[])([1,2,3]), "int[]", "'{1,2,3}'"); } diff --git a/src/dpq2/conv/to_d_types.d b/src/dpq2/conv/to_d_types.d index 471af643..6b06a036 100644 --- a/src/dpq2/conv/to_d_types.d +++ b/src/dpq2/conv/to_d_types.d @@ -12,6 +12,7 @@ import dpq2.conv.from_d_types; import dpq2.conv.numeric: rawValueToNumeric; import dpq2.conv.time: binaryValueAs, TimeStamp, TimeStampUTC, TimeOfDayWithTZ, Interval; import dpq2.conv.geometric: binaryValueAs, Line; +import dpq2.conv.inet: binaryValueAs, InetAddress, CidrAddress; import dpq2.conv.arrays : binaryValueAs; import vibe.data.json: Json, parseJsonString; diff --git a/src/dpq2/conv/to_variant.d b/src/dpq2/conv/to_variant.d index 01663c04..1a75a26c 100644 --- a/src/dpq2/conv/to_variant.d +++ b/src/dpq2/conv/to_variant.d @@ -4,6 +4,7 @@ module dpq2.conv.to_variant; import dpq2.value; import dpq2.oids: OidType; import dpq2.result: ArrayProperties; +import dpq2.conv.inet: InetAddress, CidrAddress; import dpq2.conv.to_d_types; import dpq2.conv.numeric: rawValueToNumeric; import dpq2.conv.time: TimeStampUTC; @@ -98,6 +99,12 @@ Variant toVariant(bool isNullablePayload = true)(in Value v) @safe case Date: return retVariant!PGdate; case DateArray: return retArray__!PGdate; + case HostAddress: return retVariant!InetAddress; + case HostAddressArray: return retArray__!InetAddress; + + case NetworkAddress: return retVariant!CidrAddress; + case NetworkAddressArray: return retArray__!CidrAddress; + case Time: return retVariant!PGtime_without_time_zone; case TimeArray: return retArray__!PGtime_without_time_zone; diff --git a/src/dpq2/oids.d b/src/dpq2/oids.d index 0658d4d0..9aaa08c7 100644 --- a/src/dpq2/oids.d +++ b/src/dpq2/oids.d @@ -119,6 +119,8 @@ shared static this() A(TimeStamp, TimeStampArray), A(Line, LineArray), A(Json, JsonArray), + A(NetworkAddress, NetworkAddressArray), + A(HostAddress, HostAddressArray), A(UUID, UUIDArray) ]; @@ -149,6 +151,8 @@ bool isSupportedArray(OidType t) pure nothrow @nogc case TimeArray: case TimeWithZoneArray: case NumericArray: + case NetworkAddressArray: + case HostAddressArray: case UUIDArray: case LineArray: case JsonArray: @@ -181,6 +185,7 @@ private OidType detectOidTypeNotCareAboutNullable(T)() import std.uuid : StdUUID = UUID; static import dpq2.conv.geometric; static import dpq2.conv.time; + import dpq2.conv.inet: InetAddress, CidrAddress; import vibe.data.json : VibeJson = Json; alias UT = Unqual!T; @@ -203,6 +208,8 @@ private OidType detectOidTypeNotCareAboutNullable(T)() static if(is(UT == dpq2.conv.time.TimeStamp)){ return TimeStamp; } else static if(is(UT == dpq2.conv.time.TimeStampUTC)){ return TimeStampWithZone; } else static if(is(UT == VibeJson)){ return Json; } else + static if(is(UT == InetAddress)){ return HostAddress; } else + static if(is(UT == CidrAddress)){ return NetworkAddress; } else static if(is(UT == StdUUID)){ return UUID; } else static if(is(UT == BitArray)){ return VariableBitString; } else static if(dpq2.conv.geometric.isValidPointType!UT){ return Point; } else @@ -340,8 +347,8 @@ public enum OidType : Oid PolygonArray = 1027, /// AccessControlListArray = 1034, /// MacAddressArray = 1040, /// - HostAdressArray = 1041, /// - NetworkAdressArray = 651, /// + HostAddressArray = 1041, /// + NetworkAddressArray = 651, /// CStringArray = 1263, /// TimeStampArray = 1115, /// DateArray = 1182, /// diff --git a/src/dpq2/value.d b/src/dpq2/value.d index 653b6427..ff189536 100644 --- a/src/dpq2/value.d +++ b/src/dpq2/value.d @@ -160,10 +160,15 @@ class ValueConvException : ConvException } package void throwTypeComplaint(OidType receivedType, in string expectedType, string file = __FILE__, size_t line = __LINE__) pure +{ + throwTypeComplaint(receivedType.to!string, expectedType, file, line); +} + +package void throwTypeComplaint(string receivedTypeName, in string expectedType, string file = __FILE__, size_t line = __LINE__) pure { throw new ValueConvException( ConvExceptionType.NOT_IMPLEMENTED, - "Format of the column ("~to!string(receivedType)~") doesn't match to D native "~expectedType, + "Format of the column ("~receivedTypeName~") doesn't match to D native "~expectedType, file, line ); } From 3f955923ba322747c8ba81a9ec81e7d176f67fb9 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Fri, 31 Jan 2025 12:37:52 +0300 Subject: [PATCH 2/4] Code dedup: family unsupported message moved out to unsup() --- src/dpq2/conv/inet.d | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/dpq2/conv/inet.d b/src/dpq2/conv/inet.d index eac7be9a..ef0719d3 100644 --- a/src/dpq2/conv/inet.d +++ b/src/dpq2/conv/inet.d @@ -53,7 +53,7 @@ package struct TInetAddress (bool isCIDR) return new Internet6Address(addr6, port); default: - assert(0, "Unsupported address family "~family.to!string); + assert(0, family.unsup); } } @@ -69,7 +69,7 @@ package struct TInetAddress (bool isCIDR) return format("%s/%d", createStdAddr.toAddrString, this.netmask); default: - return format("Unsupported address family %s", family.to!string); //TODO: deduplicate this code + return family.unsup; } } } @@ -120,10 +120,7 @@ InetAddress vibe2pg(VibeNetworkAddress)(VibeNetworkAddress a) break; default: - throw new ValueConvException( - ConvExceptionType.NOT_IMPLEMENTED, - "Unsupported address family: "~a.family.to!string - ); + throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, a.family.unsup); } return r; @@ -167,10 +164,7 @@ if(is(T == InetAddress) || is(T == CidrAddress)) break; default: - throw new ValueConvException( - ConvExceptionType.NOT_IMPLEMENTED, - "Unsupported address family: "~v.family.to!string - ); + throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, v.family.unsup); } hdr.addr_len = addr_net_byte_order.length.to!ubyte; @@ -207,10 +201,7 @@ if(is(T == InetAddress) || is(T == CidrAddress)) case PgFamily.PGSQL_AF_INET: lenMustBe = ipv4_addr_len; break; case PgFamily.PGSQL_AF_INET6: lenMustBe = 16; break; default: - throw new ValueConvException( - ConvExceptionType.NOT_IMPLEMENTED, - "Unsupported address family: "~hdr.family.to!string - ); + throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, hdr.family.unsup); } if(hdr.addr_len != lenMustBe && hdr.always_zero == 0) @@ -252,6 +243,11 @@ if(is(T == InetAddress) || is(T == CidrAddress)) private: +string unsup(T)(in T family) +{ + return "Unsupported address family: "~family.to!string; +} + private union AddrValue { ubyte[16] addr6; // IPv6 address in native byte order From 9fe97a2a7f9085b8d1349eec55da11bc5884e256 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Fri, 31 Jan 2025 12:43:42 +0300 Subject: [PATCH 3/4] Code dedup: throw new ValueConvException calls replaced by throwUnsup() --- src/dpq2/conv/inet.d | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/dpq2/conv/inet.d b/src/dpq2/conv/inet.d index ef0719d3..f86f8e02 100644 --- a/src/dpq2/conv/inet.d +++ b/src/dpq2/conv/inet.d @@ -120,7 +120,7 @@ InetAddress vibe2pg(VibeNetworkAddress)(VibeNetworkAddress a) break; default: - throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, a.family.unsup); + throwUnsup(a.family); } return r; @@ -164,7 +164,7 @@ if(is(T == InetAddress) || is(T == CidrAddress)) break; default: - throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, v.family.unsup); + throwUnsup(v.family); } hdr.addr_len = addr_net_byte_order.length.to!ubyte; @@ -200,8 +200,7 @@ if(is(T == InetAddress) || is(T == CidrAddress)) { case PgFamily.PGSQL_AF_INET: lenMustBe = ipv4_addr_len; break; case PgFamily.PGSQL_AF_INET6: lenMustBe = 16; break; - default: - throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, hdr.family.unsup); + default: throwUnsup(hdr.family); } if(hdr.addr_len != lenMustBe && hdr.always_zero == 0) @@ -243,6 +242,12 @@ if(is(T == InetAddress) || is(T == CidrAddress)) private: +//TODO: noreturn? +void throwUnsup(T)(T family) +{ + throw new ValueConvException(ConvExceptionType.NOT_IMPLEMENTED, family.unsup); +} + string unsup(T)(in T family) { return "Unsupported address family: "~family.to!string; From abac1deef1739c90d8e9b7777c8951a06d0bd132 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Tue, 4 Feb 2025 17:40:08 +0300 Subject: [PATCH 4/4] DDoc comments added to InetAddress and CidrAddress definitions --- src/dpq2/conv/inet.d | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dpq2/conv/inet.d b/src/dpq2/conv/inet.d index f86f8e02..67e9ff6c 100644 --- a/src/dpq2/conv/inet.d +++ b/src/dpq2/conv/inet.d @@ -15,9 +15,10 @@ enum PgFamily : ubyte { PGSQL_AF_INET6, } -alias InetAddress = TInetAddress!false; -alias CidrAddress = TInetAddress!true; +alias InetAddress = TInetAddress!false; /// Represents inet PG value +alias CidrAddress = TInetAddress!true; /// Represents cidr PG value +/// package struct TInetAddress (bool isCIDR) { PgFamily family;