refactor: swap strings with an SQL builder
Adds doug-martin/goqu as dependency.
This commit is contained in:
parent
7f97e784b6
commit
6b5a07aac0
@ -9,6 +9,8 @@ import (
|
||||
"git.rosemyrtle.work/personal-finance/server/internal/api"
|
||||
"git.rosemyrtle.work/personal-finance/server/internal/dal"
|
||||
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/labstack/echo/v4"
|
||||
@ -39,7 +41,7 @@ func main() {
|
||||
}
|
||||
|
||||
// 2. Data Access Layer
|
||||
dal := dal.DalImpl{Db: db}
|
||||
dal := dal.DalImpl{Db: db, Dialect: goqu.Dialect("postgres")}
|
||||
|
||||
// 3. HTTP server
|
||||
e := echo.New()
|
||||
|
||||
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.21.1
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/deepmap/oapi-codegen/v2 v2.1.0
|
||||
github.com/doug-martin/goqu/v9 v9.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
|
||||
26
go.sum
26
go.sum
@ -1,3 +1,4 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
@ -9,16 +10,20 @@ 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/deepmap/oapi-codegen/v2 v2.1.0 h1:I/NMVhJCtuvL9x+S2QzZKpSjGi33oDZwPRdemvOZWyQ=
|
||||
github.com/deepmap/oapi-codegen/v2 v2.1.0/go.mod h1:R1wL226vc5VmCNJUvMyYr3hJMm5reyv25j952zAVXZ8=
|
||||
github.com/doug-martin/goqu/v9 v9.0.0 h1:de8P6AsHnCPZsdKQl8lrqI8XNxU3K//jrDrI2ZeYcDs=
|
||||
github.com/doug-martin/goqu/v9 v9.0.0/go.mod h1:GPZzBWikLe6aaCT2uBT5X3MTseGBYevzWiVjMl2wIWk=
|
||||
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
|
||||
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
@ -47,6 +52,8 @@ github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+k
|
||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@ -54,6 +61,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/oapi-codegen/echo-middleware v1.0.1 h1:edYGScq1phCcuDoz9AqA9eHX+tEI1LNL5PL1lkkQh1k=
|
||||
@ -70,7 +78,10 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
@ -82,29 +93,44 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@ -5,17 +5,28 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.rosemyrtle.work/personal-finance/server/internal/entity"
|
||||
"github.com/doug-martin/goqu/v9"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type DalImpl struct {
|
||||
Db *sql.DB
|
||||
Dialect goqu.DialectWrapper
|
||||
}
|
||||
|
||||
// Table aliases
|
||||
var (
|
||||
schema = goqu.S("pfbudget")
|
||||
t = schema.Table("transactions")
|
||||
tc = schema.Table("transactions_categorized")
|
||||
b = schema.Table("banks")
|
||||
bn = schema.Table("banks_nordigen")
|
||||
c = schema.Table("categories")
|
||||
)
|
||||
|
||||
func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error) {
|
||||
log.Printf("DAL::Transaction(%v)", transactionId)
|
||||
|
||||
@ -23,15 +34,23 @@ func (dal *DalImpl) Transaction(transactionId int64) (*entity.Transaction, error
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"SELECT t.id, t.date, t.description, t.amount, tc.name",
|
||||
"FROM pfbudget.transactions t",
|
||||
"LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id",
|
||||
"WHERE t.id = $1",
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
ds := dal.Dialect.Select(
|
||||
t.Col("id"),
|
||||
t.Col("date"),
|
||||
t.Col("description"),
|
||||
t.Col("amount"),
|
||||
tc.Col("name"),
|
||||
).
|
||||
From(t).
|
||||
LeftJoin(tc, goqu.On(t.Col("id").Eq(tc.Col("id")))).
|
||||
Where(t.Col("id").Eq(transactionId))
|
||||
|
||||
rows, err := dal.Db.Query(stmt, transactionId)
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
rows, err := dal.Db.Query(sqlStr, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -51,27 +70,30 @@ func (dal *DalImpl) Transactions(limit, offset int, category *string) (entity.Tr
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"SELECT t.id, t.date, t.description, t.amount, tc.name, count(*) OVER() AS total_count",
|
||||
"FROM pfbudget.transactions t",
|
||||
"LEFT JOIN pfbudget.transactions_categorized tc ON t.id = tc.id",
|
||||
}
|
||||
args := []any{limit, offset}
|
||||
ds := dal.Dialect.Select(
|
||||
t.Col("id"),
|
||||
t.Col("date"),
|
||||
t.Col("description"),
|
||||
t.Col("amount"),
|
||||
tc.Col("name"),
|
||||
goqu.L("count(*) OVER() AS total_count"),
|
||||
).
|
||||
From(t).
|
||||
LeftJoin(tc, goqu.On(t.Col("id").Eq(tc.Col("id")))).
|
||||
Order(t.Col("date").Desc()).
|
||||
Limit(uint(limit)).
|
||||
Offset(uint(offset))
|
||||
|
||||
if category != nil {
|
||||
stmts = append(stmts, "WHERE tc.name SIMILAR TO '%' || $3 || '%'")
|
||||
args = append(args, *category)
|
||||
ds = ds.Where(tc.Col("name").ILike(*category))
|
||||
}
|
||||
stmts = append(stmts,
|
||||
"ORDER BY t.date DESC",
|
||||
"LIMIT $1",
|
||||
"OFFSET $2",
|
||||
)
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
|
||||
log.Printf("DAL::Transactions::stmt: %s", stmt)
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
rows, err := dal.Db.Query(stmt, args...)
|
||||
rows, err := dal.Db.Query(sqlStr, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@ -92,33 +114,39 @@ func (dal *DalImpl) Transactions(limit, offset int, category *string) (entity.Tr
|
||||
|
||||
var ans entity.Transactions
|
||||
for _, twc := range transactions_with_count {
|
||||
t := entity.Transaction{Id: twc.Id, Date: twc.Date, Description: twc.Description, Value: twc.Value, Category: twc.Category}
|
||||
ans = append(ans, t)
|
||||
tx := entity.Transaction{Id: twc.Id, Date: twc.Date, Description: twc.Description, Value: twc.Value, Category: twc.Category}
|
||||
ans = append(ans, tx)
|
||||
}
|
||||
return ans, transactions_with_count[0].TotalCount, nil
|
||||
}
|
||||
|
||||
func (dal *DalImpl) InsertTransaction(t entity.Transaction) (entity.Transaction, error) {
|
||||
func (dal *DalImpl) InsertTransaction(tx entity.Transaction) (entity.Transaction, error) {
|
||||
log.Print("DAL::InsertTransaction")
|
||||
|
||||
if dal.Db == nil {
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"INSERT INTO pfbudget.transactions (date, description, amount)",
|
||||
"VALUES ($1, $2, $3)",
|
||||
"RETURNING id",
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
ds := dal.Dialect.Insert(t).Rows(
|
||||
goqu.Record{
|
||||
"date": tx.Date,
|
||||
"description": tx.Description,
|
||||
"amount": tx.Value,
|
||||
}).
|
||||
Returning(t.Col("id"))
|
||||
|
||||
id := new(uint64)
|
||||
if err := dal.Db.QueryRow(stmt, t.Date, t.Description, t.Value).Scan(id); err != nil {
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return entity.Transaction{}, err
|
||||
}
|
||||
|
||||
t.Id = *id
|
||||
return t, nil
|
||||
id := new(uint64)
|
||||
if err := dal.Db.QueryRow(sqlStr, args...).Scan(id); err != nil {
|
||||
return entity.Transaction{}, err
|
||||
}
|
||||
|
||||
tx.Id = *id
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (dal *DalImpl) UpdateTransaction(id entity.TransactionId, category *entity.CategoryName) (bool, error) {
|
||||
@ -132,14 +160,17 @@ func (dal *DalImpl) UpdateTransaction(id entity.TransactionId, category *entity.
|
||||
return false, errors.New("missing category")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"UPDATE pfbudget.transactions_categorized",
|
||||
"SET name = $2",
|
||||
"WHERE id = $1",
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
// TODO(43): implement upsert logic
|
||||
ds := dal.Dialect.Update(tc).
|
||||
Set(goqu.Record{"name": *category}).
|
||||
Where(tc.Col("id").Eq(id))
|
||||
|
||||
result, err := dal.Db.Exec(stmt, id, *category)
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
result, err := dal.Db.Exec(sqlStr, args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -164,18 +195,15 @@ func (dal *DalImpl) TransactionExists(id uint64) (bool, error) {
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"SELECT EXISTS(",
|
||||
" SELECT 1",
|
||||
" FROM pfbudget.transactions",
|
||||
" WHERE id = $1",
|
||||
")",
|
||||
ds := dal.Dialect.Select(goqu.L("EXISTS(SELECT 1 FROM pfbudget.transactions WHERE id = ?)", id))
|
||||
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
|
||||
exists := new(bool)
|
||||
err := dal.Db.QueryRow(stmt, id).Scan(&exists)
|
||||
if err != nil {
|
||||
if err := dal.Db.QueryRow(sqlStr, args...).Scan(exists); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -189,15 +217,21 @@ func (dal *DalImpl) Bank(bankId string) (*entity.Bank, error) {
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"SELECT b.name, b.name, n.requisition_id",
|
||||
"FROM pfbudget.banks b",
|
||||
"JOIN pfbudget.banks_nordigen n ON b.name = n.name",
|
||||
"WHERE b.name = $1",
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
ds := dal.Dialect.Select(
|
||||
b.Col("name"),
|
||||
b.Col("name"),
|
||||
bn.Col("requisition_id"),
|
||||
).
|
||||
From(b).
|
||||
Join(bn, goqu.On(b.Col("name").Eq(bn.Col("name")))).
|
||||
Where(b.Col("name").Eq(bankId))
|
||||
|
||||
rows, err := dal.Db.Query(stmt, bankId)
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := dal.Db.Query(sqlStr, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -217,14 +251,20 @@ func (dal *DalImpl) Banks() (entity.Banks, error) {
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"SELECT b.name, b.name, n.requisition_id",
|
||||
"FROM pfbudget.banks b",
|
||||
"JOIN pfbudget.banks_nordigen n ON b.name = n.name",
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
ds := dal.Dialect.Select(
|
||||
b.Col("name"),
|
||||
b.Col("name"),
|
||||
bn.Col("requisition_id"),
|
||||
).
|
||||
From(b).
|
||||
Join(bn, goqu.On(b.Col("name").Eq(bn.Col("name"))))
|
||||
|
||||
rows, err := dal.Db.Query(stmt)
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return entity.Banks{}, err
|
||||
}
|
||||
|
||||
rows, err := dal.Db.Query(sqlStr, args...)
|
||||
if err != nil {
|
||||
return entity.Banks{}, err
|
||||
}
|
||||
@ -239,13 +279,18 @@ func (dal *DalImpl) Categories() (entity.Categories, error) {
|
||||
log.Panic("database not available")
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"SELECT c.name, c.group",
|
||||
"FROM pfbudget.categories c",
|
||||
}
|
||||
stmt := strings.Join(stmts, "\n") + "\n"
|
||||
ds := dal.Dialect.Select(
|
||||
c.Col("name"),
|
||||
c.Col("group"),
|
||||
).
|
||||
From(c)
|
||||
|
||||
rows, err := dal.Db.Query(stmt)
|
||||
sqlStr, args, err := ds.Prepared(true).ToSQL()
|
||||
if err != nil {
|
||||
return []entity.Category{}, err
|
||||
}
|
||||
|
||||
rows, err := dal.Db.Query(sqlStr, args...)
|
||||
if err != nil {
|
||||
return []entity.Category{}, err
|
||||
}
|
||||
|
||||
@ -66,12 +66,7 @@ func TestDalImpl_Transaction(t *testing.T) {
|
||||
}
|
||||
|
||||
mock.
|
||||
ExpectQuery(`
|
||||
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name
|
||||
FROM \w+\.transactions \w+
|
||||
LEFT JOIN \w+\.transactions_categorized \w+
|
||||
ON \w+\.id = \w+\.id
|
||||
WHERE \w+\.id = \$1$`).
|
||||
ExpectQuery("^SELECT.*id.*date.*description.*amount.*name.*WHERE.*id.*= \\?").
|
||||
WithArgs(tt.args.transactionId).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{"id", "date", "description", "amount", "category"}).AddRows(tt.mocks...),
|
||||
@ -114,7 +109,7 @@ func TestDalImpl_Transactions(t *testing.T) {
|
||||
{
|
||||
"SelectTransactions",
|
||||
fields{db},
|
||||
args{limit: 30, offset: 0, category: nil},
|
||||
args{limit: 30, offset: 30, category: nil},
|
||||
[][]driver.Value{
|
||||
{1, date, "income", 1000, nil, 2},
|
||||
{2, date, "expense", -10.50, nil, 2},
|
||||
@ -128,7 +123,7 @@ func TestDalImpl_Transactions(t *testing.T) {
|
||||
{
|
||||
"SelectTransactionsWithCategory",
|
||||
fields{db},
|
||||
args{limit: 30, offset: 0, category: golang.Ptr("C1")},
|
||||
args{limit: 30, offset: 30, category: golang.Ptr("C1")},
|
||||
[][]driver.Value{
|
||||
{1, date, "income", 1000, "C1", 2},
|
||||
{2, date, "expense", -10.50, nil, 2},
|
||||
@ -140,7 +135,7 @@ func TestDalImpl_Transactions(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
{
|
||||
"SelectNoTransactions", fields{db}, args{limit: 30, offset: 0, category: nil}, nil, nil, false,
|
||||
"SelectNoTransactions", fields{db}, args{limit: 30, offset: 30, category: nil}, nil, nil, false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -149,22 +144,14 @@ func TestDalImpl_Transactions(t *testing.T) {
|
||||
Db: tt.fields.Db,
|
||||
}
|
||||
|
||||
args := []driver.Value{tt.args.limit, tt.args.offset}
|
||||
stmt := `
|
||||
^SELECT \w+\.id, \w+\.date, \w+\.description, \w+\.amount, \w+\.name, count\(\*\) OVER\(\) AS total_count
|
||||
FROM \w+\.transactions \w+
|
||||
LEFT JOIN \w+\.transactions_categorized \w+
|
||||
ON \w+\.id = \w+\.id
|
||||
`
|
||||
args := []driver.Value{}
|
||||
stmt := "^SELECT.*id.*date.*description.*amount.*name.*count\\(\\*\\) OVER\\(\\) AS total_count"
|
||||
if tt.args.category != nil {
|
||||
stmt += `WHERE \w+\.name SIMILAR TO '%' || \$3 || '%'`
|
||||
stmt += ".*WHERE.*name.*ILIKE \\?"
|
||||
args = append(args, *tt.args.category)
|
||||
}
|
||||
stmt += `
|
||||
ORDER BY \w+\.date DESC
|
||||
LIMIT \$1
|
||||
OFFSET \$2
|
||||
`
|
||||
stmt += ".*ORDER BY.*date.*DESC LIMIT \\? OFFSET \\?"
|
||||
args = append(args, tt.args.limit, tt.args.offset)
|
||||
|
||||
mock.
|
||||
ExpectQuery(stmt).
|
||||
@ -227,7 +214,7 @@ func TestDalImpl_Bank(t *testing.T) {
|
||||
}
|
||||
|
||||
mock.
|
||||
ExpectQuery("^SELECT .* FROM .*banks b JOIN .*banks_nordigen n ON b.name = n.name WHERE b.name = \\$1$").
|
||||
ExpectQuery("^SELECT.*name.*name.*requisition_id.*WHERE.*name.*= \\?").
|
||||
WithArgs(tt.args.bankId).
|
||||
WillReturnRows(
|
||||
mock.
|
||||
@ -290,7 +277,7 @@ func TestDalImpl_Banks(t *testing.T) {
|
||||
}
|
||||
|
||||
mock.
|
||||
ExpectQuery("^SELECT .* FROM .*banks b JOIN .*banks_nordigen n ON b.name = n.name$").
|
||||
ExpectQuery("^SELECT.*name.*name.*requisition_id").
|
||||
WithoutArgs().
|
||||
WillReturnRows(
|
||||
mock.
|
||||
@ -346,10 +333,10 @@ func TestDalImpl_InsertTransaction(t *testing.T) {
|
||||
|
||||
mock.
|
||||
ExpectQuery(`
|
||||
INSERT INTO .* \(date, description, amount\)
|
||||
VALUES \(\$1, \$2, \$3\)
|
||||
RETURNING id`).
|
||||
WithArgs(tt.args.t.Date, tt.args.t.Description, tt.args.t.Value).
|
||||
INSERT INTO.*.*amount.*date.*description.*
|
||||
VALUES.*\?, \?, \?.*
|
||||
RETURNING.*id`).
|
||||
WithArgs(tt.args.t.Value, tt.args.t.Date, tt.args.t.Description).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{"id"}).
|
||||
AddRows([]driver.Value{tt.want.Id}),
|
||||
@ -413,10 +400,10 @@ func TestDalImpl_UpdateTransaction(t *testing.T) {
|
||||
|
||||
mock.
|
||||
ExpectExec(`
|
||||
UPDATE \w+\.transactions_categorized
|
||||
SET name = \$2
|
||||
WHERE id = \$1`).
|
||||
WithArgs(tt.args.id, tt.args.category).
|
||||
UPDATE.*transactions_categorized.*
|
||||
SET.*name.*= ?.*
|
||||
WHERE.*id.*= ?`).
|
||||
WithArgs(tt.args.category, tt.args.id).
|
||||
WillReturnResult(tt.mocks)
|
||||
|
||||
got, err := dal.UpdateTransaction(tt.args.id, tt.args.category)
|
||||
@ -460,12 +447,7 @@ func TestDalImpl_TransactionExists(t *testing.T) {
|
||||
}
|
||||
|
||||
mock.
|
||||
ExpectQuery(`
|
||||
SELECT EXISTS\(
|
||||
SELECT 1
|
||||
FROM pfbudget.transactions
|
||||
WHERE id = \$1
|
||||
\)`).
|
||||
ExpectQuery("EXISTS\\(SELECT 1 FROM pfbudget\\.transactions WHERE id = \\?\\)").
|
||||
WithArgs(tt.args.id).
|
||||
WillReturnRows(
|
||||
mock.NewRows([]string{"exists"}).
|
||||
@ -529,7 +511,7 @@ func TestDalImpl_Categories(t *testing.T) {
|
||||
}
|
||||
|
||||
mock.
|
||||
ExpectQuery(`SELECT \w+\.name, \w+\.group FROM \w+.categories \w+`).
|
||||
ExpectQuery(`SELECT.*name.*group.*FROM.*categories`).
|
||||
WithoutArgs().
|
||||
WillReturnRows(mock.NewRows([]string{"name", "group"}).AddRows(tt.mocks...))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user