Skip to content

Commit abaac7b

Browse files
authored
feat: [#358] Add Insert First methods for DB (#888)
* feat: [#358] Add Insert Update Delete methods for DB * chore: update mocks * implement methods * finish * update mod * optimize * change CreatedAt to point * fix test * fix test * remove pointer * fix test * fix test * fix test * fix test * add test --------- Co-authored-by: hwbrzzl <24771476+hwbrzzl@users.noreply.github.com>
1 parent 2cdb138 commit abaac7b

16 files changed

Lines changed: 1073 additions & 29 deletions

File tree

contracts/database/db/db.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
package db
22

3+
import "database/sql"
4+
35
type DB interface {
46
Table(name string) Query
57
}
68

79
type Query interface {
8-
Where(query any, args ...any) Query
10+
First(dest any) error
911
Get(dest any) error
12+
Insert(data any) (*Result, error)
13+
Where(query any, args ...any) Query
14+
}
15+
16+
type Result struct {
17+
RowsAffected int64
18+
}
19+
20+
type Builder interface {
21+
Exec(query string, args ...any) (sql.Result, error)
22+
Get(dest any, query string, args ...any) error
23+
Select(dest any, query string, args ...any) error
1024
}

database/db/db.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import (
1313
)
1414

1515
type DB struct {
16-
config database.Config
17-
instance *sqlx.DB
16+
builder db.Builder
17+
config database.Config
1818
}
1919

20-
func NewDB(config database.Config, instance *sqlx.DB) db.DB {
21-
return &DB{config: config, instance: instance}
20+
func NewDB(config database.Config, builder db.Builder) db.DB {
21+
return &DB{config: config, builder: builder}
2222
}
2323

2424
func BuildDB(config config.Config, connection string) (db.DB, error) {
@@ -41,5 +41,5 @@ func BuildDB(config config.Config, connection string) (db.DB, error) {
4141
}
4242

4343
func (r *DB) Table(name string) db.Query {
44-
return NewQuery(r.config, r.instance, name)
44+
return NewQuery(r.config, r.builder, name)
4545
}

database/db/db_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package db
2+
3+
import (
4+
"database/sql"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/goravel/framework/contracts/database"
10+
contractsdriver "github.com/goravel/framework/contracts/database/driver"
11+
"github.com/goravel/framework/errors"
12+
mocksconfig "github.com/goravel/framework/mocks/config"
13+
mocksdriver "github.com/goravel/framework/mocks/database/driver"
14+
)
15+
16+
func TestBuildDB(t *testing.T) {
17+
var (
18+
mockConfig *mocksconfig.Config
19+
mockDriver *mocksdriver.Driver
20+
)
21+
22+
tests := []struct {
23+
name string
24+
connection string
25+
setup func()
26+
expectedError error
27+
}{
28+
{
29+
name: "Success",
30+
connection: "mysql",
31+
setup: func() {
32+
driverCallback := func() (contractsdriver.Driver, error) {
33+
return mockDriver, nil
34+
}
35+
mockConfig.On("Get", "database.connections.mysql.via").Return(driverCallback)
36+
mockDriver.On("DB").Return(&sql.DB{}, nil)
37+
mockDriver.On("Config").Return(database.Config{Driver: "mysql"})
38+
},
39+
expectedError: nil,
40+
},
41+
{
42+
name: "Config Not Found",
43+
connection: "invalid",
44+
setup: func() {
45+
mockConfig.On("Get", "database.connections.invalid.via").Return(nil)
46+
},
47+
expectedError: errors.DatabaseConfigNotFound,
48+
},
49+
}
50+
51+
for _, test := range tests {
52+
t.Run(test.name, func(t *testing.T) {
53+
mockConfig = mocksconfig.NewConfig(t)
54+
mockDriver = mocksdriver.NewDriver(t)
55+
test.setup()
56+
57+
db, err := BuildDB(mockConfig, test.connection)
58+
if test.expectedError != nil {
59+
assert.Equal(t, test.expectedError, err)
60+
assert.Nil(t, db)
61+
} else {
62+
assert.NoError(t, err)
63+
assert.NotNil(t, db)
64+
}
65+
})
66+
}
67+
}

database/db/query.go

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,41 @@ package db
22

33
import (
44
"fmt"
5+
"sort"
56

67
sq "github.com/Masterminds/squirrel"
7-
"github.com/jmoiron/sqlx"
88

99
"github.com/goravel/framework/contracts/database"
1010
"github.com/goravel/framework/contracts/database/db"
1111
"github.com/goravel/framework/errors"
12+
"github.com/goravel/framework/support/str"
1213
)
1314

1415
type Query struct {
16+
builder db.Builder
1517
conditions Conditions
1618
config database.Config
17-
instance *sqlx.DB
1819
}
1920

20-
func NewQuery(config database.Config, instance *sqlx.DB, table string) *Query {
21+
func NewQuery(config database.Config, builder db.Builder, table string) *Query {
2122
return &Query{
23+
builder: builder,
2224
conditions: Conditions{
2325
table: table,
2426
},
25-
config: config,
26-
instance: instance,
27+
config: config,
2728
}
2829
}
2930

30-
func (r *Query) Where(query any, args ...any) db.Query {
31-
r.conditions.where = append(r.conditions.where, Where{
32-
query: query,
33-
args: args,
34-
})
31+
func (r *Query) First(dest any) error {
32+
sql, args, err := r.buildSelect()
33+
// TODO: use logger instead of println
34+
fmt.Println(sql, args, err)
35+
if err != nil {
36+
return err
37+
}
3538

36-
return r
39+
return r.builder.Get(dest, sql, args...)
3740
}
3841

3942
func (r *Query) Get(dest any) error {
@@ -44,7 +47,81 @@ func (r *Query) Get(dest any) error {
4447
return err
4548
}
4649

47-
return r.instance.Select(dest, sql, args...)
50+
return r.builder.Select(dest, sql, args...)
51+
}
52+
53+
func (r *Query) Insert(data any) (*db.Result, error) {
54+
mapData, err := convertToSliceMap(data)
55+
if err != nil {
56+
return nil, err
57+
}
58+
if len(mapData) == 0 {
59+
return &db.Result{
60+
RowsAffected: 0,
61+
}, nil
62+
}
63+
64+
sql, args, err := r.buildInsert(mapData)
65+
if err != nil {
66+
return nil, err
67+
}
68+
// TODO: use logger instead of println
69+
fmt.Println(sql, args, err)
70+
result, err := r.builder.Exec(sql, args...)
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
rowsAffected, err := result.RowsAffected()
76+
if err != nil {
77+
return nil, err
78+
}
79+
80+
return &db.Result{
81+
RowsAffected: rowsAffected,
82+
}, nil
83+
}
84+
85+
func (r *Query) Where(query any, args ...any) db.Query {
86+
q := NewQuery(r.config, r.builder, r.conditions.table)
87+
q.conditions = r.conditions
88+
q.conditions.where = append(r.conditions.where, Where{
89+
query: query,
90+
args: args,
91+
})
92+
93+
return q
94+
}
95+
96+
func (r *Query) buildInsert(data []map[string]any) (sql string, args []any, err error) {
97+
if r.conditions.table == "" {
98+
return "", nil, errors.DatabaseTableIsRequired
99+
}
100+
101+
builder := sq.Insert(r.conditions.table)
102+
if r.config.PlaceholderFormat != nil {
103+
builder = builder.PlaceholderFormat(r.config.PlaceholderFormat)
104+
}
105+
106+
first := data[0]
107+
builder = builder.SetMap(first)
108+
109+
cols := make([]string, 0, len(first))
110+
for col := range first {
111+
cols = append(cols, col)
112+
}
113+
sort.Strings(cols)
114+
builder = builder.Columns(cols...)
115+
116+
for _, row := range data {
117+
vals := make([]any, 0, len(first))
118+
for _, col := range cols {
119+
vals = append(vals, row[col])
120+
}
121+
builder = builder.Values(vals...)
122+
}
123+
124+
return builder.ToSql()
48125
}
49126

50127
func (r *Query) buildSelect() (sql string, args []any, err error) {
@@ -60,6 +137,18 @@ func (r *Query) buildSelect() (sql string, args []any, err error) {
60137
builder = builder.From(r.conditions.table)
61138

62139
for _, where := range r.conditions.where {
140+
query, ok := where.query.(string)
141+
if ok {
142+
if !str.Of(query).Trim().Contains(" ", "?") {
143+
if len(where.args) > 1 {
144+
builder = builder.Where(sq.Eq{query: where.args})
145+
} else if len(where.args) == 1 {
146+
builder = builder.Where(sq.Eq{query: where.args[0]})
147+
}
148+
continue
149+
}
150+
}
151+
63152
builder = builder.Where(where.query, where.args...)
64153
}
65154

0 commit comments

Comments
 (0)