Initial commit

This commit is contained in:
David Allen 2024-08-31 19:07:08 -06:00
commit c2e8f78d0c
Signed by: towk
GPG key ID: 793B2924A49B3A3F
12 changed files with 502 additions and 0 deletions

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/davidallendj/artisan
go 1.22.6

45
main.go Normal file
View file

@ -0,0 +1,45 @@
package main
import (
"fmt"
artisan "github.com/davidallendj/artisan/pkg/artisan"
)
func main() {
// the builder is always the starting point with artisan
var (
b = artisan.Builder{}
name = "david"
count = 1
)
// do 'create' with two different ways to add columns
fmt.Printf("create.1: %s\n", b.Create("test").WithAttribute(artisan.TABLE).AddColumns(artisan.Columns{
"count": artisan.Integer{},
"name": artisan.Text{},
}).Build())
fmt.Printf("create.2: %s\n", b.Create("test").AddColumn("count", artisan.Integer{}).AddColumn("name", artisan.Text{}).Build())
fmt.Printf("create.3: %s\n", b.Create("test").AddColumns(artisan.Columns{"count": artisan.Integer{}, "name": artisan.Text{}}).Build())
// do 'insert' with two different ways to add values
fmt.Printf("insert.1: %s\n", b.Insert("test").AddValue("count", count).Build())
fmt.Printf("insert.2: %s\n", b.Insert("test").AddValues(artisan.Values{"count": count, "name": name}).Build())
// do 'select' both with and without 'where' clause
fmt.Printf("select.1: %s\n", b.Select().From("test").Where(artisan.IsEqual("name", name)).Build())
fmt.Printf("select.2: %s\n", b.Select(artisan.AllColumns()).From("test").Build())
fmt.Printf("select.3: %s\n", b.Select("count", "name").From("test").Where("count>10").Build())
fmt.Printf("select.4: %s\n", b.Select("name").WithAttribute(artisan.DISTINCT).From("test").Where(artisan.IsGreaterThanOrEqual("count", 10)).Build())
// do 'update' to set existing values
fmt.Printf("update.1: %s\n", b.Update("test").Set(artisan.Values{"count": 10, "name": "joe"}).Where("count>3").Build())
fmt.Printf("update.2: %s\n", b.Update("test").Set(artisan.Values{"count": 10, "name": "joe"}).Where(artisan.IsGreaterThan("count", 3)).OrderBy("count").Limit(10).Offset(2).Build())
// do 'delete' and delete a single and multiple records
fmt.Printf("delete.1: %s\n", b.Delete("test").Where(artisan.IsLessThanOrEqual("count", 1)).Build())
// do 'drop' to remove a table
fmt.Printf("drop.1: %s\n", b.Drop("test").Build())
fmt.Printf("drop.2: %s\n", b.Drop("test").IfExists().Build())
}

54
pkg/artisan/builder.go Normal file
View file

@ -0,0 +1,54 @@
package artisan
import (
"fmt"
sql "github.com/davidallendj/artisan/pkg/artisan/driver"
)
type state interface {
GetBuilder() *Builder
}
type Builder struct {
Driver sql.Driver
stmt string
err error
}
type fromClause struct{ builder *Builder }
type whereClause struct{ builder *Builder }
type tablename string
type conditional string
func IsGreaterThan[T any, U any](column T, value U) conditional {
return conditional(fmt.Sprintf("%v>%v", column, ConvertValue(value)))
}
func IsGreaterThanOrEqual[T any, U any](column T, value U) conditional {
return conditional(fmt.Sprintf("%v>=%v", column, ConvertValue(value)))
}
func IsLessThan[T any, U any](column T, value U) conditional {
return conditional(fmt.Sprintf("%v<%v", column, ConvertValue(value)))
}
func IsLessThanOrEqual[T any, U any](column T, value U) conditional {
return conditional(fmt.Sprintf("%v<=%v", column, ConvertValue(value)))
}
func IsEqual[T any, U any](column T, value U) conditional {
return conditional(fmt.Sprintf("%v=%v", column, ConvertValue(value)))
}
func (b *Builder) Build() string {
var s []byte = make([]byte, len(b.stmt))
copy(s, b.stmt)
b.Reset()
return string(s)
}
func (b *Builder) Error() error {
return b.err
}
func (b *Builder) Reset() {
b.stmt = ""
}

111
pkg/artisan/create.go Normal file
View file

@ -0,0 +1,111 @@
package artisan
import (
"fmt"
"strings"
)
const (
TABLE createAttribute = iota
TRIGGER
INDEX
VIEW
)
type createAttribute int
type createColumns struct {
builder *Builder
tableName string
}
type create struct {
builder *Builder
columns []Column
}
type createOptions struct {
attribute createAttribute
}
type CreateOption func(*createOptions)
func getCreateOptions(opts ...CreateOption) *createOptions {
co := &createOptions{}
for _, opt := range opts {
opt(co)
}
return co
}
func (b *Builder) Create(tableName string) *createColumns {
// always reset string if calling create
b.Reset()
b.stmt += fmt.Sprintf("CREATE TABLE %s", tableName)
return &createColumns{builder: b, tableName: tableName}
}
func (c *createColumns) WithAttribute(attribute createAttribute) *createColumns {
switch attribute {
case TRIGGER:
// b.stmt += fmt.Sprintf("CREATE TRIGGER %s")
/* TODO */
case VIEW:
// b.stmt += fmt.Sprintf("CREATE VIEW %s")
/* TODO */
case INDEX:
/* TODO */
case TABLE:
c.builder.stmt = fmt.Sprintf("CREATE TABLE %s", c.tableName)
default:
c.builder.stmt = fmt.Sprintf("CREATE TABLE %s", c.tableName)
// TODO: do reflection and add to stmt based on *supported* data type
}
return c
}
func (c *createColumns) AddColumn(name string, typ Type) *create {
cc := &create{builder: c.builder}
return cc.AddColumn(name, typ)
}
func (c *create) AddColumn(name string, typ Type) *create {
c.columns = append(c.columns, Column{name, typ})
return c
}
func (c *createColumns) AddColumns(columns Columns) *create {
cc := &create{builder: c.builder}
return cc.AddColumns(columns)
}
func (c *create) AddColumns(columns Columns) *create {
for k, v := range columns {
c.columns = append(c.columns, Column{k, v})
}
return c
}
func (c *create) Build() string {
var columns, values string
for _, col := range c.columns {
columns += fmt.Sprintf("%s %s, ", col.Name, col.Type.Name())
}
// trim off trailing delimiter
columns = strings.TrimRight(columns, ", ")
values = strings.TrimRight(values, ", ")
return fmt.Sprintf("%s(%s);", c.builder.stmt, columns)
}
// func WithColumn(name string, _type Type) CreateOption {
// return func(co *createOptions) {
// co.columns = append(co.columns, Column{name: name, _type: _type})
// }
// }
// func WithColumns(columns Columns) CreateOption {
// return func(co *createOptions) {
// for name, _type := range columns {
// WithColumn(name, _type)
// }
// }
// }

18
pkg/artisan/delete.go Normal file
View file

@ -0,0 +1,18 @@
package artisan
import "fmt"
type deleteClause struct{ builder *Builder }
func (b *Builder) Delete(tableName string) *deleteClause {
b.stmt = fmt.Sprintf("DELETE FROM %s", tableName)
return &deleteClause{builder: b}
}
func (s *deleteClause) Where(condition conditional) *whereClause {
s.builder.stmt += fmt.Sprintf(" WHERE %s", condition)
return &whereClause{builder: s.builder}
}
// defined in `select.go`
// func (s *whereClause) Build() string

View file

@ -0,0 +1,5 @@
package sql
type Driver interface {
Build() string
}

30
pkg/artisan/drop.go Normal file
View file

@ -0,0 +1,30 @@
package artisan
import "fmt"
type drop struct {
builder *Builder
}
type dropOptional struct {
tableName string
builder *Builder
}
func (b *Builder) Drop(tableName string) *dropOptional {
b.Reset()
b.stmt += fmt.Sprintf("DROP TABLE %s", tableName)
return &dropOptional{builder: b, tableName: tableName}
}
func (d *dropOptional) IfExists() *drop {
d.builder.stmt = fmt.Sprintf("DROP TABLE IF EXISTS %s", d.tableName)
return &drop{builder: d.builder}
}
func (d *dropOptional) Build() string {
return d.builder.stmt + ";"
}
func (d *drop) Build() string {
return d.builder.stmt + ";"
}

57
pkg/artisan/insert.go Normal file
View file

@ -0,0 +1,57 @@
package artisan
import (
"fmt"
"strings"
)
type insertClause struct {
builder *Builder
columns []Column
}
type insertColumns struct {
builder *Builder
tableName string
}
func (b *Builder) Insert(tableName string) *insertColumns {
b.Reset()
b.stmt += fmt.Sprintf("INSERT INTO %s", tableName)
return &insertColumns{builder: b}
}
func (i *insertColumns) AddValue(name string, value any) *insertClause {
ic := &insertClause{builder: i.builder}
return ic.AddValue(name, value)
}
func (i *insertClause) AddValue(name string, value any) *insertClause {
i.columns = append(i.columns, Column{name, ConvertValue(value)})
return i
}
func (i *insertColumns) AddValues(values Values) *insertClause {
ic := &insertClause{builder: i.builder}
return ic.AddValues(values)
}
func (i *insertClause) AddValues(values Values) *insertClause {
for name, value := range values {
i.columns = append(i.columns, Column{name, ConvertValue(value)})
}
return i
}
func (i *insertClause) Build() string {
var columns, values string
for _, col := range i.columns {
columns += fmt.Sprintf("%s, ", col.Name)
values += fmt.Sprintf("%s, ", col.Type.Value())
}
// trim off trailing delimiter
columns = strings.TrimRight(columns, ", ")
values = strings.TrimRight(values, ", ")
return fmt.Sprintf("%s(%s) VALUES(%s);", i.builder.stmt, columns, values)
}

61
pkg/artisan/select.go Normal file
View file

@ -0,0 +1,61 @@
package artisan
import (
"fmt"
"strings"
)
const (
NONE selectAttribute = iota
DISTINCT
ALL
)
type selectAttribute int
type selectArg string
type selectClause struct{ builder *Builder }
type selectOptional struct{ builder *Builder }
// Select() initiates a new SQL using a builder object. Calling this function
// will call the builders Reset() function and clear the current statement.
func (b *Builder) Select(what ...selectArg) *selectOptional {
b.Reset()
if len(what) == 0 {
b.stmt += "SELECT *"
} else {
var output string
for _, s := range what {
output += string(s) + ", "
}
output = strings.TrimRight(output, ", ")
b.stmt += fmt.Sprintf("SELECT %s", output)
}
return &selectOptional{builder: b}
}
func (s *selectOptional) WithAttribute(attribute selectAttribute) *selectClause {
switch attribute {
case NONE:
s.builder.stmt += ""
case DISTINCT:
s.builder.stmt += " DISTINCT"
case ALL:
s.builder.stmt += " ALL"
default:
}
return &selectClause{builder: s.builder}
}
func (s *selectOptional) From(table tablename) *fromClause {
sc := &selectClause{builder: s.builder}
return sc.From(table)
}
func (s *selectClause) From(table tablename) *fromClause {
s.builder.stmt += fmt.Sprintf(" FROM %s", table)
return &fromClause{builder: s.builder}
}
func (s *fromClause) Build() string {
return s.builder.stmt + ";"
}

63
pkg/artisan/type.go Normal file
View file

@ -0,0 +1,63 @@
package artisan
import (
"fmt"
"reflect"
)
type Column struct {
Name string
Type Type
}
type Columns map[string]Type
type Values map[string]any
// define abstract interfaces
type Type interface {
Name() string
Value() string
}
// define functions
func AllColumns() selectArg {
return "*"
}
// define concrete SQLite types
type Base struct{ value string }
type Text struct{ Base }
type Integer struct{ Base }
type Real struct{ Base }
type Bool struct{ Base }
// define type name/value functions
func (b Base) Value() string { return b.value }
func (Base) Name() string { return "TEXT" }
func (Text) Name() string { return "TEXT" }
func (Integer) Name() string { return "INTEGER" }
func (Real) Name() string { return "REAL" }
func (Bool) Name() string { return "BOOLEAN" }
// define string interface functions for printing
func (t Text) String() string { return t.Value() }
func (i Integer) String() string { return i.Value() }
func (r Real) String() string { return r.Value() }
type IntegerType interface{ ~int | ~int32 | ~int64 }
func ConvertValue(value any) Type {
s := fmt.Sprint(value)
switch reflect.TypeOf(value).Kind() {
case reflect.String:
return Text{Base{fmt.Sprintf("\"%s\"", s)}}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return Integer{Base{s}}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return Integer{Base{s}}
case reflect.Float32, reflect.Float64:
return Real{Base{s}}
case reflect.Bool:
return Bool{Base{s}}
}
return Base{s}
}

43
pkg/artisan/update.go Normal file
View file

@ -0,0 +1,43 @@
package artisan
import (
"fmt"
"strings"
)
type updateClause struct{ builder *Builder }
type setClause struct{ builder *Builder }
func (b *Builder) Update(table tablename) *updateClause {
b.stmt = fmt.Sprintf("UPDATE %s", table)
return &updateClause{builder: b}
}
func (s *updateClause) Set(values Values) *setClause {
columns := ""
for name, value := range values {
columns += fmt.Sprintf("%s = %v, ", name, value)
}
s.builder.stmt += strings.TrimRight(columns, ", ")
return &setClause{builder: s.builder}
}
func (s *setClause) Where(condition conditional) *whereClause {
s.builder.stmt += fmt.Sprintf(" WHERE %s", condition)
return &whereClause{builder: s.builder}
}
func (s *whereClause) OrderBy(column string) *whereClause {
s.builder.stmt += fmt.Sprintf(" ORDER BY %s", column)
return s
}
func (s *whereClause) Limit(rowcount int) *whereClause {
s.builder.stmt += fmt.Sprintf(" LIMIT %d", rowcount)
return s
}
func (s *whereClause) Offset(offset int) *whereClause {
s.builder.stmt += fmt.Sprintf(" OFFSET %d", offset)
return s
}

12
pkg/artisan/where.go Normal file
View file

@ -0,0 +1,12 @@
package artisan
import "fmt"
func (s *fromClause) Where(condition conditional) *whereClause {
s.builder.stmt += fmt.Sprintf(" WHERE %s", condition)
return &whereClause{builder: s.builder}
}
func (s *whereClause) Build() string {
return s.builder.stmt + ";"
}