Skip to content

Commit 071da77

Browse files
committed
feat(builder): introduce the sql builder to the project
Refs: #6
1 parent 8262595 commit 071da77

4 files changed

Lines changed: 256 additions & 18 deletions

File tree

internal/builder.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package sqlok
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"strings"
8+
9+
log "github.com/sirupsen/logrus"
10+
)
11+
12+
type JoinType string
13+
14+
const (
15+
InnerJoin JoinType = "INNER JOIN"
16+
Join JoinType = "JOIN"
17+
LeftJoin JoinType = "LEFT JOIN"
18+
OuterJoin JoinType = "OUTER JOIN"
19+
RightJoin JoinType = "RIGHT JOIN"
20+
)
21+
22+
type QueryBuilder interface {
23+
Build() (string, []any)
24+
Execute(ctx context.Context, db *sql.DB) (*sql.Rows, error)
25+
}
26+
27+
type queryBuilder struct {
28+
query string
29+
args []any
30+
}
31+
32+
func NewQueryBuilder() QueryBuilder {
33+
return &queryBuilder{}
34+
}
35+
36+
func (b *queryBuilder) Build() (string, []any) {
37+
return b.query, b.args
38+
}
39+
40+
func (b *queryBuilder) Execute(ctx context.Context, db *sql.DB) (*sql.Rows, error) {
41+
query, args := b.Build()
42+
log.Info("executing query: ", query, " with args: ", args)
43+
rows, err := db.QueryContext(ctx, query, args...)
44+
if err != nil {
45+
return nil, fmt.Errorf("query execution failed: %v", err)
46+
}
47+
return rows, err
48+
}
49+
50+
type SelectBuilder interface {
51+
QueryBuilder
52+
Select(columns ...string) SelectBuilder
53+
From(table string) SelectBuilder
54+
Where(condition string, args ...interface{}) SelectBuilder
55+
And(condition string, args ...interface{}) SelectBuilder
56+
Or(condition string, args ...interface{}) SelectBuilder
57+
OrderBy(columns ...string) SelectBuilder
58+
Limit(limit int) SelectBuilder
59+
Offset(limit int) SelectBuilder
60+
Join(joinType JoinType, table string, on string) SelectBuilder
61+
}
62+
63+
type selectBuilder struct {
64+
QueryBuilder
65+
selectColumns []string
66+
fromTable string
67+
where []string
68+
whereArgs []any
69+
orderBy []string
70+
limit int
71+
offset int
72+
joins []joinInfo
73+
}
74+
75+
type joinInfo struct {
76+
joinType JoinType
77+
table string
78+
// TODO: the on string should be revewied
79+
on string
80+
}
81+
82+
func NewSelectBuiler() SelectBuilder {
83+
b := &selectBuilder{
84+
QueryBuilder: NewQueryBuilder(),
85+
}
86+
b.Clear()
87+
return b
88+
}
89+
90+
func (q *selectBuilder) Clear() SelectBuilder {
91+
q.selectColumns = []string{}
92+
q.fromTable = ""
93+
q.where = []string{}
94+
q.whereArgs = []any{}
95+
q.orderBy = []string{}
96+
q.limit = 0
97+
q.offset = 0
98+
q.joins = []joinInfo{}
99+
return q
100+
}
101+
102+
func (q *selectBuilder) Select(columns ...string) SelectBuilder {
103+
q.selectColumns = append(q.selectColumns, columns...)
104+
return q
105+
}
106+
107+
func (q *selectBuilder) From(table string) SelectBuilder {
108+
q.fromTable = table
109+
return q
110+
}
111+
112+
func (q *selectBuilder) Where(condition string, args ...any) SelectBuilder {
113+
q.where = append(q.where, condition)
114+
q.whereArgs = append(q.whereArgs, args...)
115+
return q
116+
}
117+
118+
func (q *selectBuilder) And(condition string, args ...any) SelectBuilder {
119+
return q.Where(condition, args...)
120+
}
121+
122+
func (q *selectBuilder) Or(condition string, args ...any) SelectBuilder {
123+
return q.Where(condition, args...)
124+
}
125+
126+
func (q *selectBuilder) OrderBy(columns ...string) SelectBuilder {
127+
q.orderBy = append(q.orderBy, columns...)
128+
return q
129+
}
130+
131+
func (q *selectBuilder) Limit(limit int) SelectBuilder {
132+
q.limit = limit
133+
return q
134+
}
135+
136+
func (q *selectBuilder) Offset(offset int) SelectBuilder {
137+
q.offset = offset
138+
return q
139+
}
140+
141+
func (q *selectBuilder) Join(joinType JoinType, table string, on string) SelectBuilder {
142+
q.joins = append(q.joins, joinInfo{joinType, table, on})
143+
return q
144+
}
145+
146+
func (q *selectBuilder) Build() (string, []any) {
147+
var b strings.Builder
148+
149+
b.WriteString("SELECT ")
150+
b.WriteString(strings.Join(q.selectColumns, ", "))
151+
152+
b.WriteString(" FROM ")
153+
b.WriteString(q.fromTable)
154+
155+
for _, join := range q.joins {
156+
b.WriteString(fmt.Sprintf(" %s %s ON %s ", join.joinType, join.table, join.on))
157+
}
158+
159+
// TODO: Implement the AND/OR properly
160+
if len(q.where) > 0 {
161+
b.WriteString(" WHERE ")
162+
b.WriteString(strings.Join(q.where, " AND "))
163+
}
164+
165+
if len(q.orderBy) > 0 {
166+
b.WriteString(" ORDER BY ")
167+
b.WriteString(strings.Join(q.orderBy, ", "))
168+
}
169+
170+
if q.limit > 0 {
171+
b.WriteString(fmt.Sprintf(" LIMIT %d ", q.limit))
172+
}
173+
174+
if q.offset > 0 {
175+
b.WriteString(fmt.Sprintf(" LIMIT %d ", q.offset))
176+
}
177+
args := q.whereArgs
178+
q.Clear()
179+
return b.String(), args
180+
}
181+
182+
func (b *selectBuilder) Execute(ctx context.Context, db *sql.DB) (*sql.Rows, error) {
183+
query, args := b.Build()
184+
log.Info("executing query: ", query, " with args: ", args)
185+
rows, err := db.QueryContext(ctx, query, args...)
186+
if err != nil {
187+
return nil, fmt.Errorf("query execution failed: %v", err)
188+
}
189+
return rows, err
190+
}

internal/builder_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package sqlok
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestBuilder(t *testing.T) {
11+
columns := []string{"column1", "column2"}
12+
table1 := "table1"
13+
14+
b := NewSelectBuiler()
15+
t.Run("Should select columns from table1", func(t *testing.T) {
16+
b.Select(columns...).From(table1)
17+
sql, _ := b.Build()
18+
assert.Equal(t, "SELECT "+strings.Join(columns, ", ")+" FROM table1", sql)
19+
})
20+
21+
t.Run("Should select with where clause no parameters", func(t *testing.T) {
22+
b.Select(columns...).From(table1).Where(columns[0] + "=1")
23+
sql, args := b.Build()
24+
assert.Equal(t, "SELECT "+strings.Join(columns, ", ")+" FROM table1 WHERE "+columns[0]+"=1", sql)
25+
assert.Equal(t, []any{}, args)
26+
})
27+
28+
t.Run("Should select with where clause with parameters", func(t *testing.T) {
29+
b.Select(columns...).From(table1).Where(columns[0]+"=$1", 1).And(columns[1]+"=$2", 2)
30+
sql, args := b.Build()
31+
assert.Equal(t, "SELECT "+strings.Join(columns, ", ")+" FROM table1 WHERE "+columns[0]+"=$1 AND "+columns[1]+"=$2", sql)
32+
assert.Equal(t, []any{1, 2}, args)
33+
})
34+
}

internal/sqlok.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"errors"
66
"fmt"
77

8+
sql "database/sql"
9+
810
"github.com/candango/sqlok/internal/schema"
9-
"github.com/jackc/pgx/v5"
11+
_ "github.com/jackc/pgx/v5/stdlib"
1012
)
1113

1214
type DatabaseLoader interface {
@@ -19,30 +21,32 @@ type DatabaseLoader interface {
1921
type PostgresLoader struct {
2022
cString string
2123
ctx context.Context
22-
conn *pgx.Conn
24+
db *sql.DB
2325
tables []*schema.Table
26+
builder SelectBuilder
2427
}
2528

2629
func NewPostgresLoader(cString string, ctx context.Context) DatabaseLoader {
2730
return &PostgresLoader{
2831
cString: cString,
2932
ctx: ctx,
33+
builder: NewSelectBuiler(),
3034
}
3135

3236
}
3337

3438
func (l *PostgresLoader) Connect() error {
3539
var err error
3640
l.ctx = context.Background()
37-
l.conn, err = pgx.Connect(l.ctx, l.cString)
41+
l.db, err = sql.Open("pgx", l.cString)
3842
if err != nil {
3943
return errors.New(fmt.Sprintf("Unable to connect to the database: %v\n", err))
4044
}
4145
return nil
4246
}
4347

4448
func (l *PostgresLoader) Disconnect() error {
45-
return l.conn.Close(l.ctx)
49+
return l.db.Close()
4650
}
4751

4852
func (l *PostgresLoader) Load() error {
@@ -62,15 +66,18 @@ func (l *PostgresLoader) Load() error {
6266
}
6367

6468
func (l *PostgresLoader) loadTables() ([]*schema.Table, error) {
65-
sql := `SELECT
66-
table_schema,
67-
table_name
68-
FROM
69-
information_schema.tables
70-
WHERE
71-
table_type = 'BASE TABLE' AND
72-
table_schema not in ('pg_catalog', 'information_schema');`
73-
rows, err := l.conn.Query(l.ctx, sql)
69+
l.builder.Select(
70+
"table_schema", "table_name",
71+
).From(
72+
"information_schema.tables",
73+
).Where(
74+
"table_type = 'BASE TABLE'",
75+
).And(
76+
"table_schema not in ('pg_catalog', 'information_schema')",
77+
)
78+
79+
rows, err := l.builder.Execute(l.ctx, l.db)
80+
fmt.Println(rows)
7481

7582
if err != nil {
7683
return nil, errors.New(fmt.Sprintf("Failed to run query : %v\n", err))
@@ -94,10 +101,17 @@ func (l *PostgresLoader) loadTables() ([]*schema.Table, error) {
94101
}
95102

96103
func (l *PostgresLoader) loadFields(table *schema.Table) ([]*schema.Field, error) {
97-
sql := fmt.Sprintf(`SELECT column_name, data_type
98-
FROM information_schema.columns
99-
WHERE table_schema = '%s' AND table_name = '%s'`, table.Schema, table.Name)
100-
rows, err := l.conn.Query(l.ctx, sql)
104+
l.builder.Select(
105+
"column_name", "data_type",
106+
).From(
107+
"information_schema.columns",
108+
).Where(
109+
"table_schema = $1", table.Schema,
110+
).And(
111+
"table_name = $2", table.Name,
112+
)
113+
114+
rows, err := l.builder.Execute(l.ctx, l.db)
101115

102116
if err != nil {
103117
return nil, errors.New(fmt.Sprintf("Failed to run query : %v\n", err))

internal/sqlok_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestHttpTransport(t *testing.T) {
2424
t.Errorf("error reading the tag sqlok for the field %s", field.Name)
2525
continue
2626
}
27-
t.Error(tag)
27+
// t.Error(tag)
2828
}
2929
assert.True(t, true)
3030
}

0 commit comments

Comments
 (0)