Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to represent PostgreSQL interval in Go

Tags:

postgresql

go

How to represent PostgreSQL interval in Go?

My struct looks like this:

type Product struct {
    Id              int
    Name            string
    Type            int
    Price           float64
    Execution_time  ????
}

The execution_time field on my database is interval.

like image 786
fonini Avatar asked Sep 23 '15 18:09

fonini


People also ask

How do I create an interval in PostgreSQL?

In PostgreSQL, the make_interval() function creates an interval from years, months, weeks, days, hours, minutes and seconds fields. You provide the years, months, weeks, days, hours, minutes and/or seconds fields, and it will return an interval in the interval data type.

How interval works in PostgreSQL?

In PostgreSQL, the Interval is another type of data type used to store and deploy Time in years, months, days, hours, minutes, seconds, etc. And the months and days values are integers values, whereas the second's field can be the fractions values.

Is interval data type in PostgreSQL?

In PostgreSQL the interval data type is used to store and manipulate a time period. It holds 16 bytes of space and ranging from -178, 000, 000 years to 178, 000, 000 years.

What is go in PostgreSQL?

GO is a batch statement terminator. Several statements may be executed within a batch, and GO terminates that batch. GO is important because it can be used to create a single script containing normal statements and statements that must be the only statement in a batch.


Video Answer


3 Answers

The best answer I've come across is to use bigint in your schema, and implement Value & Scan on a wrapper type for time.Duration.

// Duration lets us convert between a bigint in Postgres and time.Duration
// in Go
type Duration time.Duration

// Value converts Duration to a primitive value ready to written to a database.
func (d Duration) Value() (driver.Value, error) {
    return driver.Value(int64(d)), nil
}

// Scan reads a Duration value from database driver type.
func (d *Duration) Scan(raw interface{}) error {
    switch v := raw.(type) {
    case int64:
        *d = Duration(v)
    case nil:
        *d = Duration(0)
    default:
        return fmt.Errorf("cannot sql.Scan() strfmt.Duration from: %#v", v)
    }
    return nil
}

Unfortunately, you'll sacrifice the ability to do interval arithmetic inside queries - unless some clever fellow wants to post the type conversion for bigint => interval.

like image 102
AManOfScience Avatar answered Oct 21 '22 07:10

AManOfScience


If you're OK with conforming to the time.Duration limits and you need only seconds accuracy you could:

  • Create the table with a SECOND precision ... someInterval INTERVAL SECOND(0), ...
  • Convert INTERVAL into seconds: SELECT EXTRACT(EPOCH FROM someInterval) FROM someTable;

  • Use time.Duration::Seconds to insert data to prepared statements

like image 3
Janek Olszak Avatar answered Oct 21 '22 08:10

Janek Olszak


One solution is to wrap the time.Duration type in a wrapper type, and on it provide implementations of sql.Scanner and driver.Valuer.

// PgDuration wraps a time.Duration to provide implementations of
// sql.Scanner and driver.Valuer for reading/writing from/to a DB.
type PgDuration time.Duration

Postgres appears to be quite flexible with the format provided when inserting into an INTERVAL column. The default format returned by calling String() on a duration is accepted, so for the implementation of driver.Value, simply call it:

// Value converts the PgDuration into a string.
func (d PgDuration) Value() (driver.Value, error) {
    return time.Duration(d).String(), nil
}

When retrieving an INTERVAL value from Postgres, it returns it in a format that is not so easily parsed by Go (ex. "2 days 05:00:30.000250"), so we need to do some manual parsing in our implementation of sql.Scanner. In my case, I only care about supporting hours, minutes, and seconds, so I implemented it as follows:

// Scan converts the received string in the format hh:mm:ss into a PgDuration.
func (d *PgDuration) Scan(value interface{}) error {
    switch v := value.(type) {
    case string:
        // Convert format of hh:mm:ss into format parseable by time.ParseDuration()
        v = strings.Replace(v, ":", "h", 1)
        v = strings.Replace(v, ":", "m", 1)
        v += "s"
        dur, err := time.ParseDuration(v)
        if err != nil {
            return err
        }
        *d = PgDuration(dur)
        return nil
    default:
        return fmt.Errorf("cannot sql.Scan() PgDuration from: %#v", v)
    }
}

If you need to support other duration units, you can start with a similar approach, and handle the additional units appropriately.

Also, if you happen to be using the GORM library to automatically migrate your table, you will also want to provide an implementation of GORM's migrator.GormDataTypeInterface:

// GormDataType tells GORM to use the INTERVAL data type for a PgDuration column.
func (PgDuration) GormDataType() string {
    return "INTERVAL"
}
like image 1
robbieperry22 Avatar answered Oct 21 '22 06:10

robbieperry22