diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f79c9a3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/repo-go.iml b/.idea/repo-go.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/repo-go.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..907776b --- /dev/null +++ b/go.mod @@ -0,0 +1,26 @@ +module github.com/spacetab-io/pgrepo-go + +go 1.18 + +require ( + github.com/Masterminds/squirrel v1.5.3 // indirect + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgx-zap v0.0.0-20220909013905-c08a18c611dd // indirect + github.com/jackc/pgx/v5 v5.1.1 // indirect + github.com/jackc/puddle/v2 v2.1.2 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spacetab-io/configuration-structs-go/v2 v2.0.0-alpha4 // indirect + github.com/stretchr/testify v1.8.1 // indirect + go.uber.org/atomic v1.10.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.23.0 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 // indirect + golang.org/x/text v0.3.8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..207814a --- /dev/null +++ b/go.sum @@ -0,0 +1,54 @@ +github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= +github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgx-zap v0.0.0-20220909013905-c08a18c611dd h1:M+2auCrSjdkb5QTP1KMF5UguRJwhaotv65kp35fmvYs= +github.com/jackc/pgx-zap v0.0.0-20220909013905-c08a18c611dd/go.mod h1:MgU77f9s+s9Xl39aYLbOmxd5iKzeIyD5pdnHERuCquE= +github.com/jackc/pgx/v5 v5.1.1 h1:pZD79K1SYv8wc2HmCQA6VdmRQi7/OtCfv9bM3WAXUYA= +github.com/jackc/pgx/v5 v5.1.1/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk= +github.com/jackc/puddle/v2 v2.1.2 h1:0f7vaaXINONKTsxYDn4otOAiJanX/BMeAtY//BXqzlg= +github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spacetab-io/configuration-structs-go/v2 v2.0.0-alpha4 h1:WxpQZOZ960I+YoC9ZUgz8rUPQHfDhtC6GGAd9fvUQig= +github.com/spacetab-io/configuration-structs-go/v2 v2.0.0-alpha4/go.mod h1:/qyni0G7nIAu2Hdp7VW3p+RwjhwvIKJtKdbbN2osywE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= +golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/interface.go b/interface.go new file mode 100644 index 0000000..6378c43 --- /dev/null +++ b/interface.go @@ -0,0 +1,41 @@ +package repo + +import ( + "context" + + "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type RepositoryInterface interface { + GetReader() *pgxpool.Pool + GetWriter() *pgxpool.Pool + GetRepoName() string + + GetPrefix() string + TableName(args ...string) string + TableNameWithoutPrefix() string + Column(columnName string, prefix ...string) string + ColumnWithoutPrefix(columnName string) string + Columns(specificColumns ...string) []string + ColumnsWithoutPrefix(specificCols ...string) []string + ColumnExists(column string) bool + + Join(foreignColumns, thisTableColumn string, additionalWheres ...squirrel.Sqlizer) (string, []any) + JoinOn(prefix string, on string) string + + ReadRow(ctx context.Context, _sql string, args []any) (*QueryResults, error) + ReadRows(ctx context.Context, _sql string, args []any) (*QueryResults, error) + WriteData(ctx context.Context, _sql string, args []any) error + WriteDataAndReadRow(ctx context.Context, _sql string, args []any) (*QueryResults, error) + WriteDataAndReadRows(ctx context.Context, _sql string, args []any) (*QueryResults, error) + + ReadRowsError(err error) error + ScanRowError(err error) error + ScanRowsError(err error) error + ScanCountRow(row pgx.Row) (int, error) + + TsVectorFromColumn(lang ColumnsLang, columns ...string) squirrel.Sqlizer + TsVectorFromData(lang ColumnsLang, args ...string) squirrel.Sqlizer +} diff --git a/main.functions.internal_test.go b/main.functions.internal_test.go new file mode 100644 index 0000000..934e954 --- /dev/null +++ b/main.functions.internal_test.go @@ -0,0 +1,53 @@ +package repo + +import ( + "testing" + + "github.com/Masterminds/squirrel" + "github.com/stretchr/testify/assert" + "gitlab.worldskills.ru/worldskills/dpws/etc.git/v5/configuration" +) + +var testLimits = &configuration.Limits{ + TitleLen: 4096, + TextLen: 12800, +} + +func TestRepositoryStuff_tsVector(t *testing.T) { + type testCase struct { + name string + in []string + exp squirrel.Sqlizer + } + + tcs := []testCase{ + { + name: "один аргумент", + in: []string{"question"}, + exp: squirrel.Expr(`to_tsvector('russian', lower(question))`), + }, + { + name: "два аргумента", + in: []string{"question", "answer"}, + exp: squirrel.Expr(`to_tsvector('russian', lower(question || ' ' || answer))`), + }, + { + name: "без аргументов", + in: nil, + exp: nil, + }, + } + + t.Parallel() + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + r := RepositoryStuff{} + + assert.Equal(t, tc.exp, r.tsVectorFromColumn(ColumnLangRu, tc.in...)) + }) + } +} diff --git a/postgresConnect.go b/postgresConnect.go new file mode 100644 index 0000000..e7fb53d --- /dev/null +++ b/postgresConnect.go @@ -0,0 +1,65 @@ +package repo + +import ( + "context" + "fmt" + + zapadapter "github.com/jackc/pgx-zap" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/tracelog" + "github.com/spacetab-io/configuration-structs-go/v2/contracts" + "go.uber.org/zap" +) + +const ( + failureCode = 1 +) + +func PGConnect(cfg contracts.DatabaseCfgInterface, logger *zap.Logger, logLvl string) (*pgxpool.Pool, error) { + pgxConfig, err := pgxpool.ParseConfig(cfg.GetDSN()) + if err != nil { + return nil, err + } + + pgxConfig.MaxConnLifetime, pgxConfig.MaxConns, pgxConfig.MinConns = cfg.GetConnectionParams() + + pgxConfig.ConnConfig.RuntimeParams = map[string]string{"standard_conforming_strings": "on"} + pgxConfig.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error { + // Так как БД esat находится на той же СУБД, что и de-api, мы получаем + // наследование глобального datestyle и на бд esat. Чтобы поменять datestyle, + // нужно поменять в конфиге postgres для всех БД, что может убить работу + // de-api. Для того чтобы обойти это, мы выставим datestyle для коннекта, + // который будет использовать только esat. И волки целы и овцы сыты. + // Но конфигурацию БД нужно бы поменять... + rows, err := conn.Query(ctx, "SET datestyle = 'ISO, DMY'") + if err != nil { + return fmt.Errorf("AfterConnect SET datestyle error: %w", err) + } + + rows.Close() + + return nil + } + + trLevel, err := tracelog.LogLevelFromString(logLvl) + if err != nil { + return nil, err + } + + pgxConfig.ConnConfig.Tracer = &tracelog.TraceLog{ + Logger: zapadapter.NewLogger(logger.WithOptions(zap.WithCaller(false))), + LogLevel: trLevel, + } + + db, err := pgxpool.NewWithConfig(context.Background(), pgxConfig) + if err != nil { + return nil, err + } + + if err := db.Ping(context.Background()); err != nil { + return nil, err + } + + return db, nil +} diff --git a/postgresRepo.go b/postgresRepo.go new file mode 100644 index 0000000..f546534 --- /dev/null +++ b/postgresRepo.go @@ -0,0 +1,339 @@ +package repo + +import ( + "context" + "fmt" + "strings" + + "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type PostgresRepo struct { + name string + reader *pgxpool.Pool + writer *pgxpool.Pool + table string + prefix string + cols []string +} + +type QueryResults struct { + poolCon *pgxpool.Conn + rows pgx.Rows + row pgx.Row +} + +type ColumnsLang string + +const ( + ColumnLangRu ColumnsLang = "russian" + ColumnLangEn ColumnsLang = "english" +) + +func NewPostgresRepo(reader, writer *pgxpool.Pool, name, table, prefix string, columns []string) *PostgresRepo { + return &PostgresRepo{ + name: name, + reader: reader, + writer: writer, + table: table, + prefix: prefix, + cols: columns, + } +} + +func (r *PostgresRepo) SetRepoName(name string) { + r.name = name +} + +func (r PostgresRepo) GetRepoName() string { + return r.name +} + +func (r *PostgresRepo) SetReader(reader *pgxpool.Pool) { + r.reader = reader +} + +func (r PostgresRepo) GetReader() *pgxpool.Pool { + return r.reader +} + +func (r *PostgresRepo) SetWriter(writer *pgxpool.Pool) { + r.writer = writer +} + +func (r PostgresRepo) GetWriter() *pgxpool.Pool { + return r.writer +} + +func (r *PostgresRepo) SetTable(table string) { + r.table = table +} + +func (r *PostgresRepo) GetTable() string { + return r.table +} + +func (r *PostgresRepo) SetPrefix(prefix string) { + r.prefix = prefix +} + +func (r PostgresRepo) GetPrefix() string { + return r.prefix +} + +func (r *PostgresRepo) SetCols(cols []string) { + r.cols = cols +} + +func (r *PostgresRepo) GetCols() []string { + return r.cols +} + +func (r PostgresRepo) Column(name string, args ...string) string { + prefix := r.prefix + + if len(args) >= 1 { + prefix = args[0] + } + + if prefix != "" { + return fmt.Sprintf("%s.%s", prefix, name) + } + + return name +} + +func (r PostgresRepo) ColumnWithoutPrefix(name string) string { + str := strings.Split(name, ".") + if len(str) <= 1 { + return name + } + + return str[1] +} + +func (r PostgresRepo) Columns(specificColumns ...string) []string { + cols := r.cols + if len(specificColumns) > 0 { + cols = specificColumns + } + + cc := make([]string, 0, len(cols)) + + for _, c := range r.cols { + cc = append(cc, fmt.Sprintf("%s.%s", r.prefix, c)) + } + + return cc +} + +func (r PostgresRepo) ColumnsWithoutPrefix(specificColumns ...string) []string { + cols := make([]string, 0, len(r.Columns(specificColumns...))) + + if len(specificColumns) == 0 { + specificColumns = r.Columns() + } + + for _, col := range specificColumns { + str := strings.Split(col, ".") + if len(str) <= 1 { + cols = append(cols, col) + + continue + } + + cols = append(cols, str[1]) + } + + return cols +} + +func (r PostgresRepo) ColumnExists(column string) bool { + columnSlice := strings.Split(column, ".") + + if len(columnSlice) == 2 { //nolint:gomnd // структура наименования с определением названия таблицы -- 2 элемента + column = columnSlice[1] + } + + for _, col := range r.ColumnsWithoutPrefix() { + if col == column { + return true + } + } + + return false +} + +func (r PostgresRepo) TableName(args ...string) string { + prefix := r.prefix + + if len(args) >= 1 { + prefix = args[0] + } + + return fmt.Sprintf("%s %s", r.table, prefix) +} + +func (r PostgresRepo) TableNameWithoutPrefix() string { + return r.table +} + +func (r PostgresRepo) JoinOn(prefix string, on string) string { + if prefix == "" { + prefix = r.prefix + } + + return r.TableName(prefix) + " on " + on +} + +func (r PostgresRepo) Join(foreignColumns, thisTableColumn string, additionalWheres ...squirrel.Sqlizer) (string, []any) { + stmt := fmt.Sprintf("%s on %s = %s", r.TableName(), thisTableColumn, foreignColumns) + + if len(additionalWheres) == 0 { + return stmt, nil + } + + args := make([]any, 0) + + for _, where := range additionalWheres { + if where == nil { + continue + } + + q, moreArgs, _ := where.ToSql() //nolint:errcheck // полагаем, что ошибок в формировании подзапроса не будет + + stmt += fmt.Sprintf(" AND %s", q) + + if len(moreArgs) > 0 { + args = append(args, moreArgs...) + } + } + + return stmt, args +} + +func (r PostgresRepo) ReadRows(ctx context.Context, _sql string, args []any) (*QueryResults, error) { + db, err := r.GetReader().Acquire(ctx) + if err != nil { + return nil, fmt.Errorf("%s.readRows Acquire error: %w", r.GetRepoName(), err) + } + + rows, err := db.Query(ctx, _sql, args...) + if err != nil { + return nil, fmt.Errorf("%s.readRows Query error: %w", r.GetRepoName(), err) + } + + return &QueryResults{poolCon: db, rows: rows}, nil +} + +func (r PostgresRepo) ReadRow(ctx context.Context, _sql string, args []any) (*QueryResults, error) { + db, err := r.GetReader().Acquire(ctx) + if err != nil { + return nil, fmt.Errorf("%s.ReadRow Acquire error: %w", r.GetRepoName(), err) + } + + return &QueryResults{poolCon: db, row: db.QueryRow(ctx, _sql, args...)}, nil +} + +func (r PostgresRepo) WriteData(ctx context.Context, _sql string, args []any) error { + db, err := r.GetWriter().Acquire(ctx) + if err != nil { + return fmt.Errorf("%s.writeData Acquire error: %w", r.GetRepoName(), err) + } + + defer db.Release() + + if _, err := db.Exec(ctx, _sql, args...); err != nil { + return fmt.Errorf("%s.writeData query error: %w", r.GetRepoName(), err) + } + + return nil +} + +func (r PostgresRepo) WriteDataAndReadRow(ctx context.Context, _sql string, args []any) (*QueryResults, error) { + db, err := r.GetWriter().Acquire(ctx) + if err != nil { + return nil, fmt.Errorf("%s.WriteDataAndReadRow Acquire error: %w", r.GetRepoName(), err) + } + + return &QueryResults{poolCon: db, row: db.QueryRow(ctx, _sql, args...)}, nil +} + +func (r PostgresRepo) WriteDataAndReadRows(ctx context.Context, _sql string, args []any) (*QueryResults, error) { + db, err := r.GetWriter().Acquire(ctx) + if err != nil { + return nil, fmt.Errorf("%s.WriteDataAndReadRows Acquire error: %w", r.GetRepoName(), err) + } + + rows, err := db.Query(ctx, _sql, args...) + if err != nil { + return nil, fmt.Errorf("%s.WriteDataAndReadRows Query error: %w", r.GetRepoName(), err) + } + + return &QueryResults{poolCon: db, rows: rows}, nil +} + +func (r PostgresRepo) ReadRowsError(err error) error { + return fmt.Errorf("%s row scan error: %w", r.GetRepoName(), err) +} + +func (r PostgresRepo) ScanRowError(err error) error { + return fmt.Errorf("%s row scan error: %w", r.GetRepoName(), err) +} + +func (r PostgresRepo) ScanRowsError(err error) error { + return fmt.Errorf("%s rows scan error: %w", r.GetRepoName(), err) +} + +func (r PostgresRepo) ScanCountRow(row pgx.Row) (int, error) { + var count int + + if err := row.Scan(&count); err != nil { + return 0, err + } + + return count, nil +} + +func (r PostgresRepo) TsVectorFromColumn(lang ColumnsLang, columns ...string) squirrel.Sqlizer { + if len(columns) == 0 { + return nil + } + + return squirrel.Expr(fmt.Sprintf( + `to_tsvector('%s', lower(%s))`, + lang, + strings.Join(columns, " || ' ' || "), + )) +} + +func (r PostgresRepo) TsVectorFromData(lang ColumnsLang, args ...string) squirrel.Sqlizer { + if len(args) == 0 { + return nil + } + + return squirrel.Expr(fmt.Sprintf( + `to_tsvector('%s', lower('%s'))`, + lang, + strings.Join(args, "' || ' ' || '"), + // r.collate(lang), + )) +} + +func (qr QueryResults) Close() { + if qr.rows != nil { + qr.rows.Close() + } + + qr.poolCon.Release() +} + +func (qr *QueryResults) GetRows() pgx.Rows { + return qr.rows +} + +func (qr *QueryResults) GetRow() pgx.Row { + return qr.row +}