Skip to content

Commit

Permalink
Fix issues with mapping of TIMESTAMPZ[] postgres type
Browse files Browse the repository at this point in the history
Prior to this PR, sqlc was unable to map `TIMESTAMPZ[]` to `[]time.Time`
due a limitation in the `pq` driver. This PR leverages a suggested fix
from a Github issue in the `pq` repo.
  • Loading branch information
dmjb committed Jul 11, 2024
1 parent a441c32 commit 32a686b
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 18 deletions.
6 changes: 3 additions & 3 deletions internal/db/eval_history.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/db/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions internal/db/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024 Stacklok, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// PgTime/PgTimeArray is used to work around the pq driver's inability to map
// TIMESTAMPZ[] to []time.Time without some hand holding.
// Code taken mostly as-is from: https://github.com/lib/pq/issues/536#issuecomment-397849980

package db

import (
"database/sql/driver"
"errors"
"time"

"github.com/lib/pq"
)

// ErrParseData signifies that an error occurred while parsing SQL data
var ErrParseData = errors.New("unable to parse SQL data")

// PgTime wraps a time.Time
type PgTime struct{ time.Time }

// Scan implements the sql.Scanner interface
func (t *PgTime) Scan(val interface{}) error {
switch v := val.(type) {
case time.Time:
t.Time = v
return nil
case []uint8: // byte is the same as uint8: https://golang.org/pkg/builtin/#byte
_t, err := pq.ParseTimestamp(nil, string(v))
if err != nil {
return ErrParseData
}
t.Time = _t
return nil
case string:
_t, err := pq.ParseTimestamp(nil, v)
if err != nil {
return ErrParseData
}
t.Time = _t
return nil
}
return ErrParseData
}

// Value implements the driver.Valuer interface
func (t *PgTime) Value() (driver.Value, error) { return pq.FormatTimestamp(t.Time), nil }

// PgTimeArray wraps a time.Time slice to be used as a Postgres array
// type PgTimeArray []time.Time
type PgTimeArray []PgTime

//type PgTimeArray []pq.NullTime

// Scan implements the sql.Scanner interface
func (a *PgTimeArray) Scan(src interface{}) error {
return pq.GenericArray{A: a}.Scan(src)
}

// Value implements the driver.Valuer interface
func (a *PgTimeArray) Value() (driver.Value, error) {
return pq.GenericArray{A: a}.Value()
}
4 changes: 2 additions & 2 deletions internal/history/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ func (_ *evaluationHistoryService) updateExistingStatus(
ctx context.Context,
qtx db.Querier,
evaluationID uuid.UUID,
times []time.Time,
times db.PgTimeArray,
) error {
// if the status is repeated, then just append the current timestamp to it
times = append(times, time.Now())
times = append(times, db.PgTime{Time: time.Now()})
return qtx.UpdateEvaluationTimes(ctx, db.UpdateEvaluationTimesParams{
EvaluationTimes: times,
ID: evaluationID,
Expand Down
6 changes: 3 additions & 3 deletions internal/history/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,21 +747,21 @@ var (
RuleEntityID: ruleEntityID,
Status: db.EvalStatusTypesError,
Details: errTest.Error(),
EvaluationTimes: []time.Time{time.Now()},
EvaluationTimes: db.PgTimeArray{db.PgTime{Time: time.Now()}},
}
differentDetails = db.EvaluationStatus{
ID: evaluationID,
RuleEntityID: ruleEntityID,
Status: db.EvalStatusTypesError,
Details: "something went wrong",
EvaluationTimes: []time.Time{time.Now()},
EvaluationTimes: db.PgTimeArray{db.PgTime{Time: time.Now()}},
}
differentState = db.EvaluationStatus{
ID: evaluationID,
RuleEntityID: ruleEntityID,
Status: db.EvalStatusTypesSkipped,
Details: engerr.ErrEvaluationSkipped.Error(),
EvaluationTimes: []time.Time{time.Now()},
EvaluationTimes: db.PgTimeArray{db.PgTime{Time: time.Now()}},
}
errTest = errors.New("oh no")
)
Expand Down
25 changes: 16 additions & 9 deletions sqlc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ packages:
queries: "./database/query/"
schema: "./database/migrations/"
engine: "postgresql"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: true
emit_exact_table_names: false
emit_empty_slices: true
overrides:
- db_type: profile_selector
go_type:
type: "ProfileSelector"
gen:
go:
out: "./internal/db/"
package: "db"
emit_json_tags: true
emit_prepared_queries: false
emit_interface: true
emit_exact_table_names: false
emit_empty_slices: true
overrides:
- db_type: profile_selector
go_type:
type: "ProfileSelector"
- column: "evaluation_statuses.evaluation_times"
go_type:
type: "PgTimeArray"

0 comments on commit 32a686b

Please sign in to comment.