question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

NpgsqlRange improvements

See original GitHub issue

The problem

Working with NpgsqlRange I’ve found that the following use cases are a big cumbersome to work with:

  • Modifying an existing range
  • Infinite values
  • Nullable values

Modifying an existing range

Often times I’ve found that you need to reduce or extend an existing range and I’ve solved it with:

var oldRange = ...;
var newRange = new NpgsqlRange<T>(
    oldRange.LowerBound, oldRange.LowerBoundIsInclusive, oldRange.LowerBoundIsInfinite,
    newUpperBound, ..., ...);

This copies three values (the actual bound value, whether it’s inclusive, whether it’s infinite). In general any operation when you really need to have one of the bounds in a single variable is difficult to work with due to the fact that you have to carry over the extra 2 satellite values.

Infinite values

Postgres documentation specifies that:

The lower bound of a range can be omitted, meaning that all points less than the upper bound are included in the range. Likewise, if the upper bound of the range is omitted, then all points greater than the lower bound are included in the range. If both lower and upper bounds are omitted, all values of the element type are considered to be in the range.

This is equivalent to considering that the lower bound is “minus infinity”, or the upper bound is “plus infinity”, respectively. But note that these infinite values are never values of the range’s element type, and can never be part of the range. (So there is no such thing as an inclusive infinite bound — if you try to write one, it will automatically be converted to an exclusive bound.)

(emphasis mine)

This implies that, for example, new NpgsqlRange<T>(default(T), true, true, ...) and new NpgsqlRange<T>(default(T), false, true) is exactly the same thing and represents a range with an open left bound.

The main problem with the current interface is that in order for you to specify an infinite bound on either side of the range you need to also specify two extra values (the value and the inclusiveness) which don’t make much sense.

Nullable types

I think that in the context of a NpgsqlRange<T>, T should always be non nullable and null should always be allowed as a value for either side of the range. This greatly helps when you have to assemble a range based on two different nullable values.

For example:

T? from = ...;
T? to = ...;

Currently in order to construct a range I would need to write:

new NpgsqlRange<T>(
    from.GetValueOrDefault(), ..., !from.HasValue,
    to.GetValueOrDefault(), ..., !to.HasValue);

which, as mentioned above, is rather redundant.

Proposed solution

Considering the above issues I propose that we store bounds in appropriate data structures so that their shape can be easily constructed and passed around.

Example implementation

    public class Bound<T>
        where T : struct // see below (*1)
    {
        public T Value { get; set; }
        public bool IsInclusive { get; set; }
        public bool IsInfinite { get; set; }

        public static Bound<T> Include(T? bound)
        {
            return new Bound<T>
            {
                Value = bound.GetValueOrDefault(),
                IsInclusive = bound.HasValue,
                IsInfinite = !bound.HasValue,
            };
        }

        public static Bound<T> Exclude(T? bound)
        {
            return new Bound<T>
            {
                Value = bound.GetValueOrDefault(),
                IsInclusive = false,
                IsInfinite = !bound.HasValue,
            };
        }

        public static Bound<T> Open()
        {
            return new Bound<T>
            {
                Value = default(T),
                IsInclusive = false,
                IsInfinite = true,
            };
        }
    }

In addition to this we would need to extend NpgsqlRange with:

  • A constructor that accepts two Bound<T>s
  • Two getters that return the corresponding Lower and Upper bounds as Bound<T>s

*(1) I’m relatively new to C# and I’d like to support reference types, but I can’t seem to find a way to define that while also allowing T?. I believe in C# 8 (with a specific enabled extension) this is a non-issue given that they “fixed” default nullability for reference types.

    public class Bound<T>
        where T : struct // see below (*1)
    {
        public static Bound<T> Open()
        {
            return new Bound<T>
            {
                Value = bound.GetValueOrDefault(),
                IsInclusive = false,
                IsInfinite = !bound.HasValue,
            };
        }
        ...
    }

Usage examples:

In order to modify an existing range you could have:

var oldRange = ...;
var newRange = new NpgsqlRange<T>(oldRange.Lower, Bound<T>.Include(newUpperBound));

For open bounds:

new NpgsqlRange<T>(Bound<T>.Open(), Bound<T>.Exclude(upperBound));

For nullable types:

T? from = ...;
T? to = ...;
new NpgsqlRange<T>(Bound<T>.Include(from), Bound<T>.Exclude(to));

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:11 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
rojicommented, Jan 7, 2019

Finally, I’m personally interested in seeing a code sample with the current API that shows that it’s really so bad. I’ve occasionally worked with ranges and with the 6-param constructor, and while it’s not ideal I can’t say it was that problematic or terrible an experience (considering the time we’re spending discussing this 😃).

Oh yeah sure, it’s nitpicking and I’m sure there are other more important issues out there that are definitely more prioritized. It’s just that this is a fairly trivial change (once an API is decided) and can improve the range interface quite a bit.

I’m sorry if I came across as overly negative on this… Part of the discussion above was complicated with nullability questions and C# 8.0, struct vs. class, etc.

Again, I don’t think C# 8 introduces any changes that affect this (namely, the possibility to have a non-constrained generic type and to use T? within it). Regarding introducing something like Bound<T>, even if I personally don’t see a big need, I think if a full API proposal is submitted we’d definitely consider it - I really do mean that. This doesn’t mean we’d necessarily accept it - it could be argued that instantiating two Bound<T> instances to construct an NpgsqlRange<T> would in fact be more verbose/complicated than the current 6-param constructor, which is why we need a full proposal with a comparison to the current API, etc.

1reaction
shoooecommented, Jan 7, 2019

@austindrenski I’d appreciate it, thanks. 😄

Read more comments on GitHub >

github_iconTop Results From Across the Web

6.0 Release Notes | Npgsql Documentation
New features. Timestamp rationalization and improvements. Support for timestamp with time zone and timestamp without time zone has been ...
Read more >
Rediscovering Postgres and EF core | Shawty's Live Space
... I've worked out in my head where improvements needed to be made, ... the “OnShiftPeriods” has a data type of “NpgsqlRange<T>”.
Read more >
Npgsql full (with EFCore and internals) - Slides
The performance improvement is dramatic. · The further away the server is, the more the speedup (think cloud). · Supported for all CRUD...
Read more >
c# - Range type with Npgsql
Edit: I found the NpgsqlRange type, but I'm getting the error 'NpgsqlRange<int>' which is not supported by current database provider .
Read more >
Application Development Guide - Fujitsu Enterprise Postgres
Fujitsu Enterprise Postgres upgrades contain feature improvements and enhancements that may ... performance improvements. ... NpgsqlRange<LocalDateTim.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found