From f2ebd5b53f139199ad27359e7565c90db6dccc05 Mon Sep 17 00:00:00 2001 From: Aneurin Barker Snook Date: Tue, 7 Jul 2026 22:07:50 +0100 Subject: [PATCH] add update statement --- insert_test.go | 4 ++ select.go | 13 +++-- table.go | 29 ++++++++++++ update.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ update_test.go | 41 ++++++++++++++++ 5 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 update.go create mode 100644 update_test.go diff --git a/insert_test.go b/insert_test.go index df4a989..5331a7b 100644 --- a/insert_test.go +++ b/insert_test.go @@ -29,6 +29,10 @@ func TestInsert(t *testing.T) { Args: []any{1234, "Adam"}, String: "insert or update into customer (id, name) values (?, ?)", }, + { + In: Insert("customer").Columns("name").Select(Select("baby_names").Columns("name")), + String: "insert into customer (name) select name from baby_names", + }, } for _, testCase := range testCases { diff --git a/select.go b/select.go index 48f0ff2..6fcc0f6 100644 --- a/select.go +++ b/select.go @@ -30,6 +30,10 @@ func (q *SelectQuery) Args() []any { } func (q *SelectQuery) Columns(defs ...string) *SelectQuery { + if q.columns == nil { + q.columns = []Column{} + } + for _, def := range defs { q.columns = append(q.columns, Column(def)) } @@ -157,6 +161,10 @@ func (q *SelectQuery) String() string { } func (q *SelectQuery) Where(def string, args ...any) *SelectQuery { + if q.where == nil { + q.where = []Where{} + } + q.where = append(q.where, Where(def)) if len(args) > 0 { @@ -172,10 +180,7 @@ func (q *SelectQuery) Where(def string, args ...any) *SelectQuery { func Select(from string) *SelectQuery { q := &SelectQuery{ - columns: []Column{}, - tables: []SelectTable{From(from)}, - - where: []Where{}, + tables: []SelectTable{From(from)}, } return q diff --git a/table.go b/table.go index 1bb731b..2f4f95d 100644 --- a/table.go +++ b/table.go @@ -9,6 +9,7 @@ import ( var ( insertTableRegexp = regexp.MustCompile("^into ([^ ]+)( (as) ([^ ]+))?$") selectTableRegexp = regexp.MustCompile("^(from|left join|inner join|right join) ([^ ]+)( (as) ([^ ]+))?( (on) ([^ ]+) (=|!=|>|>=|<|<=) ([^ ]+)( (and) ([^ ]+) (=|!=|>|>=|<|<=) ([^ ]+))*)?$") + updateTableRegexp = regexp.MustCompile("^([^ ]+)( (as) ([^ ]+))?$") ) type InsertTable string @@ -120,3 +121,31 @@ func RightJoin(def string) SelectTable { return SelectTable(fmt.Sprintf("right join %s", def)) } + +type UpdateTable string + +func (t UpdateTable) IsValid() bool { + return insertTableRegexp.MatchString(string(t)) +} + +func (t UpdateTable) Parse() (table, as string, ok bool) { + if insertTableRegexp.MatchString(string(t)) { + ok = true + + result := insertTableRegexp.FindAllStringSubmatch(string(t), -1) + + for _, r := range result { + table = r[1] + + if r[3] == "as" { + as = r[4] + } + } + } + + return +} + +func (t UpdateTable) String() string { + return string(t) +} diff --git a/update.go b/update.go new file mode 100644 index 0000000..072ffd1 --- /dev/null +++ b/update.go @@ -0,0 +1,126 @@ +package sqimple + +import ( + "slices" + "strings" +) + +// https://www.sqlite.org/lang_update.html +type UpdateStatement struct { + table UpdateTable + or string + + columns []string + set map[string]any + + where []Where + args []any +} + +func (s *UpdateStatement) Args() []any { + args := []any{} + + for _, column := range s.columns { + args = append(args, s.set[column]) + } + + for _, arg := range s.args { + args = append(args, arg) + } + + return args +} + +func (s *UpdateStatement) IsValid() bool { + if !s.table.IsValid() { + return false + } + + for _, w := range s.where { + if !w.IsValid() { + return false + } + } + + return false +} + +func (s *UpdateStatement) Or(or string) *UpdateStatement { + s.or = or + return s +} + +func (s *UpdateStatement) Set(column string, value any) *UpdateStatement { + if s.columns == nil { + s.columns = []string{} + } + + if s.set == nil { + s.set = map[string]any{} + } + + if !slices.Contains(s.columns, column) { + s.columns = append(s.columns, column) + } + + s.set[column] = value + + return s +} + +func (s *UpdateStatement) SetMap(data map[string]any) *UpdateStatement { + for column, value := range data { + s.Set(column, value) + } + + return s +} + +func (s *UpdateStatement) String() string { + strs := []string{"update"} + + if s.or != "" { + strs = append(strs, "or", s.or) + } + + strs = append(strs, s.table.String(), "set") + + for i, column := range s.columns { + if i < len(s.columns)-1 { + strs = append(strs, column, "= ?,") + } else { + strs = append(strs, column, "= ?") + } + } + + if len(s.where) > 0 { + strs = append(strs, "where") + + for _, w := range s.where { + strs = append(strs, w.String()) + } + } + return strings.Join(strs, " ") +} + +func (s *UpdateStatement) Where(def string, args ...any) *UpdateStatement { + s.where = append(s.where, Where(def)) + + if len(args) > 0 { + if s.args == nil { + s.args = []any{} + } + + s.args = append(s.args, args...) + } + + return s +} + +func Update(table string) *UpdateStatement { + s := &UpdateStatement{ + table: UpdateTable(table), + } + + return s +} diff --git a/update_test.go b/update_test.go new file mode 100644 index 0000000..eb577d9 --- /dev/null +++ b/update_test.go @@ -0,0 +1,41 @@ +package sqimple + +import ( + "testing" + + "github.com/alecthomas/assert/v2" +) + +func TestUpdate(t *testing.T) { + type TestCase struct { + In *UpdateStatement + Args []any + String string + } + + testCases := []TestCase{ + { + In: Update("customer").Set("name", "Adam"), + Args: []any{"Adam"}, + String: "update customer set name = ?", + }, + { + In: Update("customer").Set("name", "Adam").Where("id = ?", 1234), + Args: []any{"Adam", 1234}, + String: "update customer set name = ? where id = ?", + }, + { + // Same as previous, but backwards + In: Update("customer").Where("id = ?", 1234).Set("name", "Adam"), + Args: []any{"Adam", 1234}, + String: "update customer set name = ? where id = ?", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.String, func(t *testing.T) { + assert.Equal(t, testCase.Args, testCase.In.Args(), "collected arguments incorrectly") + assert.Equal(t, testCase.String, testCase.In.String(), "stringified incorrectly") + }) + } +}