Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Postgres arrays returned as string #53

Closed
ghost opened this issue Sep 29, 2020 · 9 comments
Closed

Postgres arrays returned as string #53

ghost opened this issue Sep 29, 2020 · 9 comments
Milestone

Comments

@ghost
Copy link

ghost commented Sep 29, 2020

I have this table:

create table sessions (
    id uuid primary key default gen_random_uuid(),
    user_id uuid not null references users(id) on delete cascade,
    identifier text not null default '',
    scopes text[] not null default '{}'
);

However, it generates this code:

type Sessions struct {
	ID         uuid.UUID `sql:"primary_key"`
	UserID     uuid.UUID
	Identifier string
	Scopes     string
}

scopes has been emitted as string rather than []string, and results in the funny Postgres {foo, bar} string. Is it possible to scan directly into an array?

@go-jet
Copy link
Owner

go-jet commented Sep 29, 2020

Jet currently doesn't have support for arrays.
Until it gets support you can do something like this:

type Session struct {
    model.Sessions
}

func (s Session) GetScopes() []string {
  return ... convert s.Scopes to []string
}

func (s* Session) SetScopes(scopes []string) {
   s.Scopes = ... convert []string into postgres array representation.
}

@ghost
Copy link
Author

ghost commented Sep 29, 2020

Thanks, here's my sketchy workaround for pgx:

package pg

import (
	"github.com/jackc/pgtype"
)

func DecodeTextArray(s string) ([]string, error) {
	ta := &pgtype.TextArray{}
	if err := ta.Scan(s); err != nil {
		return nil, err
	}

	arr := make([]string, len(ta.Elements))
	for i, text := range ta.Elements {
		arr[i] = text.String
	}

	return arr, nil
}

func EncodeTextArray(arr []string) (string, error) {
	elements := make([]pgtype.Text, len(arr))
	for i, s := range arr {
		elements[i] = pgtype.Text{
			Status: pgtype.Present,
			String: s,
		}
	}

	v, err := (&pgtype.TextArray{
		Elements: elements,
		Dimensions: []pgtype.ArrayDimension{
			{Length: int32(len(arr)), LowerBound: 1},
		},
		Status: pgtype.Present,
	}).Value()
	if err != nil {
		return "", err
	}

	if v == nil {
		return "{}", nil
	}

	return v.(string), nil
}

@go-jet
Copy link
Owner

go-jet commented Jul 27, 2021

Ability to change field type of generated model type is now added to develop branch. Sample usage can be seen here.
For jet to be able to handle string arrays correctly a new type is needed. This type will handle conversion from and to db array format.
For instance:

type StringArray []string

func (s StringArray) Scan(value interface{}) error {
   ...
}

func (s StringArray) Value() (driver.Value, error) {
  ...
}

@go-jet go-jet added this to the Version 2.6.0 milestone Oct 25, 2021
@go-jet
Copy link
Owner

go-jet commented Oct 25, 2021

Ability to change field type of generated model type is included in v2.6.0 release.

@go-jet go-jet closed this as completed Oct 25, 2021
@sadensmol
Copy link

Hi @go-jet can you please clarify , how this could be used with cli generator?

@go-jet
Copy link
Owner

go-jet commented Jan 9, 2024

It can't be used from cli generator, only from Generator customization.

@sadensmol
Copy link

thanks for the clarification! :(

@Hetch3t
Copy link

Hetch3t commented Feb 24, 2024

For anyone looking for implementation, here what works for me:

import (
	"database/sql/driver"
	"strings"
)

type StringArray []string

func (s *StringArray) Scan(value interface{}) error {
	input := strings.Trim(value.(string), "{}")
	*s = StringArray(strings.Split(input, ","))

	return nil
}

func (s StringArray) Value() (driver.Value, error) {
	return "{" + strings.Join(s, ",") + "}", nil
}

And in your custom generator you should add:

func useArray(f *template.TableModelField, t metadata.Table, c metadata.Column) {
	if c.DataType.Kind == metadata.ArrayType {
		if c.DataType.Name == "text" {
			// Note that StringArray should be in separate package
			f.Type = template.NewType(typings.StringArray{})
		}
	}
}

And a few lines of code later:

...

UseModel(template.DefaultModel().
	UseTable(func(t metadata.Table) template.TableModel {
		return template.DefaultTableModel(t).
			UseField(func(c metadata.Column) template.TableModelField {
				defaultTableModelField := template.DefaultTableModelField(c)

				useArray(&defaultTableModelField, t, c)

				return defaultTableModelField
			})
	})

...

@adriantofan
Copy link

instead of defining a new type it is possible to use directly pq.StringArray in the generator, which is a bit more sophisticated at the implementation level

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants