initial commit

This commit is contained in:
lqs 2018-11-18 22:11:43 +08:00
commit 1268d4b2ef
17 changed files with 1508 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Liu Qishuai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

58
README.md Normal file
View File

@ -0,0 +1,58 @@
<img src="https://raw.githubusercontent.com/lqs/sqlingo/master/logo.png" width="236" height="106">
**sqlingo** is a SQL DSL & ORM library in Go. It generates code from your database and lets you write SQL queries easily.
WARNING: sqlingo is still under development. It's expected to be released by end of November 2018.
## Tutorial
### Prepare your database
In order to generate code, sqlingo requires your tables are already created in the database.
### Install sqlingo code generator
```
$ go get -u github.com/lqs/sqlingo/sqlingo-gen
```
### Generate code for your database
```
$ mkdir -p generated/sqlingo
$ sqlingo-gen root:123456@/database_name >generated/sqlingo/database_name.dsl.go
```
### Start using sqlingo
Create `main.go` to use the generated code
```
import (
"github.com/lqs/sqlingo"
"./generated/sqlingo"
)
func main() {
db, err := sqlingo.Open("mysql", "root:123456@/database_name")
if err != nil {
panic(err)
}
# insert some rows
customer1 := &CustomerModel{name: "Customer One"}
customer2 := &CustomerModel{name: "Customer Two"}
db.InsertInto(Customer).
Values(customer1, customer2).
Execute()
# do some queries
var customers []*CustomerModel
db.SelectFrom(Customer).
Where(Customer.Id.In(1, 2)).
Fetch(&customers)
# more examples
var customerId int64
var orderId int64
db.Select(Customer.Id, Order.Id).
From(Customer, Order).
Where(Customer.Id.Equals(Order.CustomerId), Order.Id.Equals(1)).
Fetch(&customerId, &orderId)
}
```

14
alias.go Normal file
View File

@ -0,0 +1,14 @@
package sqlingo
type Alias interface {
GetSQL() string
}
type alias struct {
expression Expression
name string
}
func (a *alias) GetSQL() string {
return a.expression.GetSQL() + " AS " + getSQLForName(a.name)
}

148
common.go Normal file
View File

@ -0,0 +1,148 @@
package sqlingo
import (
"fmt"
"runtime"
"strings"
)
type Model interface {
GetTable() Table
GetValues() []interface{}
}
type Assignment interface {
GetSQL() string
}
type assignment struct {
Assignment
field Field
value interface{}
}
func (a *assignment) GetSQL() string {
value, _ := getSQLFromWhatever(a.value)
return a.field.GetSQL() + " = " + value
}
func Raw(sql string) UnknownExpression {
return &expression{sql: sql, priority: 99}
}
func And(expressions ...BooleanExpression) (result BooleanExpression) {
if len(expressions) == 0 {
result = &expression{sql: "TRUE"}
return
}
for i, condition := range expressions {
if i == 0 {
result = condition
} else {
result = result.And(condition)
}
}
return
}
func Or(expressions ...BooleanExpression) (result BooleanExpression) {
if len(expressions) == 0 {
result = &expression{sql: "FALSE"}
return
}
for i, condition := range expressions {
if i == 0 {
result = condition
} else {
result = result.Or(condition)
}
}
return
}
func Function(name string, args ...Expression) Expression {
return &expression{sql: name + "(" + commaExpressions(args) + ")"}
}
func If(predicate Expression, trueValue Expression, falseValue Expression) (result Expression) {
return Function("IF", predicate, trueValue, falseValue)
}
func commaFields(fields []Field) string {
sql := ""
for i, item := range fields {
if i > 0 {
sql += ", "
}
sql += item.GetSQL()
}
return sql
}
func commaExpressions(expressions []Expression) string {
sql := ""
for i, item := range expressions {
if i > 0 {
sql += ", "
}
sql += item.GetSQL()
}
return sql
}
func commaValues(values []interface{}) string {
sql := ""
for i, item := range values {
if i > 0 {
sql += ", "
}
value, _ := getSQLFromWhatever(item)
sql += value
}
return sql
}
func commaAssignments(assignments []assignment) string {
sql := ""
for i, item := range assignments {
if i > 0 {
sql += ", "
}
sql += item.GetSQL()
}
return sql
}
func commaOrderBys(orderBys []OrderBy) string {
sql := ""
for i, item := range orderBys {
if i > 0 {
sql += ", "
}
sql += item.GetSQL()
}
return sql
}
func getSQLForName(name string) string {
// TODO: check reserved words
return "`" + name + "`"
}
func getCallerInfo() string {
for i := 0; true; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
segs := strings.Split(file, "/")
name := segs[len(segs)-1]
switch name {
case "common.go", "select.go", "insert.go", "update.go", "delete.go":
continue
default:
return fmt.Sprintf("/* %s:%d */ ", name, line)
}
}
return ""
}

81
cursor.go Normal file
View File

@ -0,0 +1,81 @@
package sqlingo
import (
"database/sql"
"fmt"
"reflect"
)
type Cursor interface {
Next() bool
Scan(dest ...interface{}) error
Close() error
}
type cursor struct {
rows *sql.Rows
}
func (c *cursor) Next() bool {
return c.rows.Next()
}
func preparePointers(val reflect.Value, scans *[]interface{}) error {
kind := val.Kind()
switch kind {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64,
reflect.String:
*scans = append(*scans, val.Addr().Interface())
case reflect.Slice:
case reflect.Struct:
for j := 0; j < val.NumField(); j++ {
field := val.Field(j)
if field.Kind() == reflect.Interface {
continue
}
*scans = append(*scans, field.Addr().Interface())
}
case reflect.Ptr:
toType := val.Type().Elem()
switch toType.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64,
reflect.String:
*scans = append(*scans, val.Addr().Interface())
default:
to := reflect.New(toType).Elem()
val.Set(to.Addr())
preparePointers(to, scans)
}
default:
println("unknown type %s", kind.String())
return fmt.Errorf("unknown type %s", kind.String())
}
return nil
}
func (c *cursor) Scan(dest ...interface{}) error {
var scans []interface{}
for i, item := range dest {
if reflect.ValueOf(item).Kind() != reflect.Ptr {
return fmt.Errorf("argument %d is not pointer", i)
}
val := reflect.Indirect(reflect.ValueOf(item))
preparePointers(val, &scans)
}
err := c.rows.Scan(scans...)
return err
}
func (c *cursor) Close() error {
return c.rows.Close()
}

62
database.go Normal file
View File

@ -0,0 +1,62 @@
package sqlingo
import (
"database/sql"
"golang.org/x/text/language"
"golang.org/x/text/message"
"time"
)
type Database struct {
db *sql.DB
debugMode bool
dialect string
}
func (d *Database) SetDebugMode(debugMode bool) {
d.debugMode = debugMode
}
func Open(driverName string, dataSourceName string) (db *Database, err error) {
var sqlDB *sql.DB
if dataSourceName != "" {
sqlDB, err = sql.Open(driverName, dataSourceName)
if err != nil {
return
}
}
db = &Database{
dialect: driverName,
db: sqlDB,
}
return
}
func (d *Database) GetDB() *sql.DB {
return d.db
}
func (d *Database) Query(sql string) (Cursor, error) {
startTime := time.Now().UnixNano()
rows, err := d.db.Query(sql)
endTime := time.Now().UnixNano()
printLog(endTime-startTime, sql)
if err != nil {
return nil, err
}
return &cursor{rows: rows}, nil
}
func (d *Database) Execute(sql string) (sql.Result, error) {
startTime := time.Now().UnixNano()
result, err := d.db.Exec(sql)
endTime := time.Now().UnixNano()
printLog(endTime-startTime, sql)
return result, err
}
var printer = message.NewPrinter(language.English)
func printLog(duration int64, sql string) {
printer.Printf("[%9d µs] %s\n", duration/1000, sql)
}

52
delete.go Normal file
View File

@ -0,0 +1,52 @@
package sqlingo
import (
"database/sql"
)
type DeleteWithTable interface {
Where(conditions ...BooleanExpression) DeleteWithWhere
}
type DeleteWithWhere interface {
GetSQL() (string, error)
Execute() (result sql.Result, err error)
}
type deleteStatus struct {
database *Database
table *Table
where *BooleanExpression
}
func (s *deleteStatus) copy() *deleteStatus {
delete_ := *s
return &delete_
}
func (d *Database) DeleteFrom(table Table) DeleteWithTable {
return &deleteStatus{database: d, table: &table}
}
func (s *deleteStatus) Where(conditions ...BooleanExpression) DeleteWithWhere {
delete_ := s.copy()
condition := And(conditions...)
delete_.where = &condition
return delete_
}
func (s *deleteStatus) GetSQL() (string, error) {
sqlString := getCallerInfo() + "DELETE FROM " + (*s.table).GetSQL() + " WHERE " + (*s.where).GetSQL()
return sqlString, nil
}
func (s *deleteStatus) Execute() (sql.Result, error) {
sqlString, err := s.GetSQL()
if err != nil {
return nil, err
}
return s.database.Execute(sqlString)
}

272
expression.go Normal file
View File

@ -0,0 +1,272 @@
package sqlingo
import (
"reflect"
"strconv"
"strings"
)
type Expression interface {
GetSQL() string
getOperatorPriority() int
NotEquals(other interface{}) BooleanExpression
Equals(other interface{}) BooleanExpression
LessThan(other interface{}) BooleanExpression
LessThanOrEquals(other interface{}) BooleanExpression
GreaterThan(other interface{}) BooleanExpression
GreaterThanOrEquals(other interface{}) BooleanExpression
IsNull() BooleanExpression
IsNotNull() BooleanExpression
In(values ...interface{}) BooleanExpression
Between(min interface{}, max interface{}) BooleanExpression
Desc() OrderBy
As(alias string) Alias
}
type BooleanExpression interface {
Expression
And(other interface{}) BooleanExpression
Or(other interface{}) BooleanExpression
Not() BooleanExpression
}
type NumberExpression interface {
Expression
Add(other interface{}) NumberExpression
Sub(other interface{}) NumberExpression
Mul(other interface{}) NumberExpression
Div(other interface{}) NumberExpression
IntDiv(other interface{}) NumberExpression
Mod(other interface{}) NumberExpression
Sum() NumberExpression
}
type StringExpression interface {
Expression
}
type UnknownExpression interface {
Expression
And(other interface{}) BooleanExpression
Or(other interface{}) BooleanExpression
Not() BooleanExpression
Add(other interface{}) NumberExpression
Sub(other interface{}) NumberExpression
Mul(other interface{}) NumberExpression
Div(other interface{}) NumberExpression
IntDiv(other interface{}) NumberExpression
Mod(other interface{}) NumberExpression
Sum() NumberExpression
}
type expression struct {
sql string
priority int
}
func (e *expression) As(name string) Alias {
return &alias{expression: e, name: name}
}
func (e *expression) GetSQL() string {
return e.sql
}
func getSQLFromWhatever(value interface{}) (sql string, priority int) {
switch value.(type) {
case Expression:
return value.(Expression).GetSQL(), value.(Expression).getOperatorPriority()
case Assignment:
return value.(Assignment).GetSQL(), 0
case int, int8, int16, int32, int64:
return strconv.FormatInt(reflect.ValueOf(value).Int(), 10), 0
case uint, uint8, uint16, uint32, uint64:
return strconv.FormatUint(reflect.ValueOf(value).Uint(), 10), 0
case string:
return "\"" + strings.Replace(value.(string), "\"", "\\\"", -1) + "\"", 0
case []interface{}:
return "(" + commaValues(value.([]interface{})) + ")", 0
default:
if value == nil {
return "NULL", 0
}
v := reflect.ValueOf(value)
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return "NULL", 0
}
return getSQLFromWhatever(reflect.Indirect(v).Interface())
}
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return getSQLFromWhatever(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return getSQLFromWhatever(v.Uint())
default:
return "[invalid type " + v.Kind().String() + "]", 99
}
}
}
/*
1 INTERVAL
2 BINARY, COLLATE
3 !
4 - (unary minus), ~ (unary bit inversion)
5 ^
6 *, /, DIV, %, MOD
7 -, +
8 <<, >>
9 &
10 |
11 = (comparison), <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN
12 BETWEEN, CASE, WHEN, THEN, ELSE
13 NOT
14 AND, &&
15 XOR
16 OR, ||
17 = (assignment), :=
*/
func (e *expression) NotEquals(other interface{}) BooleanExpression {
return e.binaryOperation("<>", other, 11)
}
func (e *expression) Equals(other interface{}) BooleanExpression {
return e.binaryOperation("=", other, 11)
}
func (e *expression) LessThan(other interface{}) BooleanExpression {
return e.binaryOperation("<", other, 11)
}
func (e *expression) LessThanOrEquals(other interface{}) BooleanExpression {
return e.binaryOperation("<=", other, 11)
}
func (e *expression) GreaterThan(other interface{}) BooleanExpression {
return e.binaryOperation(">", other, 11)
}
func (e *expression) GreaterThanOrEquals(other interface{}) BooleanExpression {
return e.binaryOperation(">=", other, 11)
}
func (e *expression) And(other interface{}) BooleanExpression {
return e.binaryOperation("AND", other, 14)
}
func (e *expression) Or(other interface{}) BooleanExpression {
return e.binaryOperation("OR", other, 16)
}
func (e *expression) Add(other interface{}) NumberExpression {
return e.binaryOperation("+", other, 7)
}
func (e *expression) Sub(other interface{}) NumberExpression {
return e.binaryOperation("-", other, 7)
}
func (e *expression) Mul(other interface{}) NumberExpression {
return e.binaryOperation("*", other, 6)
}
func (e *expression) Div(other interface{}) NumberExpression {
return e.binaryOperation("/", other, 6)
}
func (e *expression) IntDiv(other interface{}) NumberExpression {
return e.binaryOperation("DIV", other, 6)
}
func (e *expression) Mod(other interface{}) NumberExpression {
return e.binaryOperation("%", other, 6)
}
func (e *expression) Sum() NumberExpression {
return e.function("SUM")
}
func (e *expression) binaryOperation(operator string, value interface{}, priority int) *expression {
left := e.GetSQL()
leftLevel := e.priority
right, rightLevel := getSQLFromWhatever(value)
if leftLevel > priority {
left = "(" + left + ")"
}
if rightLevel >= priority {
right = "(" + right + ")"
}
return &expression{
sql: left + " " + operator + " " + right,
priority: priority,
}
}
func (e *expression) function(name string) *expression {
return &expression{sql: name + "(" + e.GetSQL() + ")", priority: 0}
}
func (e *expression) IsNull() BooleanExpression {
return &expression{sql: e.GetSQL() + " IS NULL", priority: 11}
}
func (e *expression) Not() BooleanExpression {
return &expression{sql: "NOT " + e.GetSQL(), priority: 13}
}
func (e *expression) IsNotNull() BooleanExpression {
return &expression{sql: e.GetSQL() + " IS NOT NULL", priority: 11}
}
func (e *expression) In(values ...interface{}) BooleanExpression {
if len(values) == 1 {
firstValue := values[0]
valueOfFirstValue := reflect.ValueOf(firstValue)
if valueOfFirstValue.Kind() == reflect.Slice {
length := valueOfFirstValue.Len()
values = make([]interface{}, length)
for i := 0; i < length; i++ {
value := valueOfFirstValue.Index(i)
values[i] = value.Interface()
}
}
}
if len(values) == 0 {
return &expression{sql: "FALSE", priority: 0}
}
sql := e.GetSQL() + " IN ("
for i, value := range values {
if i > 0 {
sql += ", "
}
valueSql, _ := getSQLFromWhatever(value)
sql += valueSql
}
sql += ")"
return &expression{sql: sql, priority: 11}
}
func (e *expression) Between(min interface{}, max interface{}) BooleanExpression {
minSql, _ := getSQLFromWhatever(min)
maxSql, _ := getSQLFromWhatever(max)
sql := e.GetSQL() + " BETWEEN " + minSql + " AND " + maxSql
return &expression{sql: sql, priority: 12}
}
func (e *expression) getOperatorPriority() int {
return e.priority
}
func (e *expression) Desc() OrderBy {
return &orderBy{by: e, desc: true}
}
func NewExpression(sql string, priority int) {
}

37
field.go Normal file
View File

@ -0,0 +1,37 @@
package sqlingo
type Field interface {
Expression
}
type NumberField interface {
NumberExpression
}
type BooleanField interface {
BooleanExpression
}
type StringField interface {
StringExpression
}
func newFieldExpression(tableName string, fieldName string) *expression {
sql := getSQLForName(fieldName)
if tableName != "" {
sql = getSQLForName(tableName) + "." + sql
}
return &expression{sql: sql, priority: 0}
}
func NewNumberField(tableName string, fieldName string) NumberField {
return newFieldExpression(tableName, fieldName)
}
func NewBooleanField(tableName string, fieldName string) BooleanField {
return newFieldExpression(tableName, fieldName)
}
func NewStringField(tableName string, fieldName string) StringField {
return newFieldExpression(tableName, fieldName)
}

213
generator.go Normal file
View File

@ -0,0 +1,213 @@
package sqlingo
import (
"database/sql"
"errors"
"go/format"
"regexp"
"strconv"
"unicode"
)
func convertCase(s string) (result string) {
nextCharShouldBeUpperCase := true
for _, ch := range s {
if ch == '_' {
nextCharShouldBeUpperCase = true
} else {
if nextCharShouldBeUpperCase {
result += string(unicode.ToUpper(ch))
nextCharShouldBeUpperCase = false
} else {
result += string(ch)
}
}
}
return
}
func getType(s string, nullable bool) (goType string, fieldClass string, err error) {
r, _ := regexp.Compile("([a-z]+)(\\(([0-9]+)\\))?")
submatches := r.FindStringSubmatch(s)
fieldType := submatches[1]
fieldSize := 0
if submatches[3] != "" {
fieldSize, err = strconv.Atoi(submatches[3])
if err != nil {
return
}
}
switch fieldType {
case "tinyint":
goType = "int8"
fieldClass = "NumberField"
case "smallint":
goType = "int16"
fieldClass = "NumberField"
case "int":
goType = "int32"
fieldClass = "NumberField"
case "bigint":
goType = "int64"
fieldClass = "NumberField"
case "float", "double":
goType = "float64"
fieldClass = "NumberField"
case "char", "varchar", "text", "mediumtext", "longtext", "enum", "datetime":
goType = "string"
fieldClass = "StringField"
case "blob":
goType = "[]byte"
fieldClass = "StringField"
case "bit":
if fieldSize == 1 {
goType = "bool"
fieldClass = "BooleanField"
} else {
goType = "string"
fieldClass = "StringField"
}
}
if nullable {
goType = "*" + goType
}
return
}
func wrapQuote(s string) string {
return "\"" + s + "\""
}
func Generate(driverName string, dataSourceName string, tableNames *[]string) (string, error) {
mysql, err := sql.Open(driverName, dataSourceName)
if err != nil {
return "", err
}
rows, err := mysql.Query("SELECT DATABASE()")
if err != nil {
return "", err
}
var dbName *string
if rows.Next() {
err := rows.Scan(&dbName)
if err != nil {
return "", err
}
}
if dbName == nil {
return "", errors.New("no database selected")
}
code := "package " + *dbName + "_dsl\n"
code += "import . \"github.com/lqs/sqlingo\"\n"
if tableNames == nil {
tableNames = &[]string{}
rows, err := mysql.Query("SHOW TABLES")
if err != nil {
return "", err
}
for rows.Next() {
var name string
rows.Scan(&name)
*tableNames = append(*tableNames, name)
}
}
for _, tableName := range *tableNames {
rows, err := mysql.Query("SHOW FULL COLUMNS FROM " + getSQLForName(tableName))
if err != nil {
return "", err
}
tableLines := ""
modelLines := ""
objectLines := "\tTable: NewTable(\"" + tableName + "\"),\n"
classLines := ""
className := convertCase(tableName)
tableStructName := "t" + className
modelClassName := className + "Model"
fields := ""
values := ""
for rows.Next() {
columns, err := rows.Columns()
if err != nil {
return "", err
}
var pointers []interface{}
for i := 0; i < len(columns); i++ {
var value *string
pointers = append(pointers, &value)
}
err = rows.Scan(pointers...)
if err != nil {
return "", err
}
row := make(map[string]string)
for i, column := range columns {
pointer := *pointers[i].(**string)
if pointer != nil {
row[column] = *pointer
}
}
goName := convertCase(row["Field"])
goType, fieldClass, _ := getType(row["Type"], row["Null"] == "YES")
fieldStructName := "f" + className + goName
tableLines += "\t" + goName + " *" + fieldStructName + "\n"
modelLines += "\t" + goName + " " + goType + "\n"
objectLines += "\t" + goName + ": &" + fieldStructName + "{"
objectLines += "New" + fieldClass + "(" + wrapQuote(tableName) + ", " + wrapQuote(row["Field"]) + ")},\n"
classLines += "type " + fieldStructName + " struct{ " + fieldClass + " }\n"
fields += "t." + goName + ", "
values += "m." + goName + ", "
}
//println(tableLines, recordLines, objectLines, classLines)
code += "type " + tableStructName + " struct {\n\tTable\n"
code += tableLines
code += "}\n\n"
code += classLines
code += "var " + className + " = &" + tableStructName + "{\n"
code += objectLines
code += "}\n\n"
code += "func (t t" + className + ") GetFields() []Field {\n"
code += "\treturn []Field{" + fields + "}\n"
code += "}\n\n"
code += "type " + modelClassName + " struct {\n"
//code += "\tModel\n"
code += modelLines
code += "}\n\n"
code += "func (m " + modelClassName + ") GetTable() Table {\n"
code += "\treturn " + className + "\n"
code += "}\n\n"
code += "func (m " + modelClassName + ") GetValues() []interface{} {\n"
code += "\treturn []interface{}{" + values + "}\n"
code += "}\n\n"
}
codeOut, err := format.Source([]byte(code))
if err != nil {
return "", err
}
return string(codeOut), nil
}

147
insert.go Normal file
View File

@ -0,0 +1,147 @@
package sqlingo
import (
"database/sql"
"reflect"
)
type insertStatus struct {
database *Database
table *Table
fields []Field
values []interface{}
models []Model
onDuplicateKeyUpdateAssignments []assignment
}
func (s *insertStatus) copy() *insertStatus {
insert := *s
s.fields = append([]Field{}, s.fields...)
s.values = append([]interface{}{}, s.values...)
s.onDuplicateKeyUpdateAssignments = append([]assignment{}, s.onDuplicateKeyUpdateAssignments...)
return &insert
}
type InsertWithTable interface {
Fields(fields ... Field) InsertWithValues
Values(values ... interface{}) InsertWithValues
Models(models ... interface{}) InsertWithModels
}
type InsertWithValues interface {
Values(values ... interface{}) InsertWithValues
OnDuplicateKeyUpdate() InsertWithOnDuplicateKeyUpdateBegin
Execute() (result sql.Result, err error)
}
type InsertWithModels interface {
Models(models ... interface{}) InsertWithModels
OnDuplicateKeyUpdate() InsertWithOnDuplicateKeyUpdateBegin
GetSQL() (string, error)
Execute() (result sql.Result, err error)
}
type InsertWithOnDuplicateKeyUpdateBegin interface {
Set(Field Field, value interface{}) InsertWithOnDuplicateKeyUpdate
}
type InsertWithOnDuplicateKeyUpdate interface {
Set(Field Field, value interface{}) InsertWithOnDuplicateKeyUpdate
GetSQL() (string, error)
Execute() (result sql.Result, err error)
}
func (d *Database) InsertInto(table Table) InsertWithTable {
return &insertStatus{database: d, table: &table}
}
func (s *insertStatus) Fields(fields ... Field) InsertWithValues {
insert := s.copy()
insert.fields = fields
return insert
}
func (s *insertStatus) Values(values ... interface{}) InsertWithValues {
insert := s.copy()
insert.values = append(insert.values, values)
return insert
}
func (s *insertStatus) addModel(model interface{}) {
m0, ok := model.(Model)
if ok {
s.models = append(s.models, m0)
return
}
value := reflect.ValueOf(model)
if value.Kind() == reflect.Ptr {
value = reflect.Indirect(value)
s.addModel(value.Interface())
}
if value.Kind() == reflect.Slice {
for i := 0; i < value.Len(); i++ {
elem := value.Index(i)
addr := elem.Addr()
inter := addr.Interface()
s.addModel(inter)
}
return
}
}
func (s *insertStatus) Models(models ... interface{}) InsertWithModels {
if len(models) == 0 {
return s
}
insert := s.copy()
for _, model := range models {
insert.addModel(&model)
}
return insert
}
func (s *insertStatus) OnDuplicateKeyUpdate() InsertWithOnDuplicateKeyUpdateBegin {
insert := s.copy()
return insert
}
func (s *insertStatus) Set(field Field, value interface{}) InsertWithOnDuplicateKeyUpdate {
insert := s.copy()
insert.onDuplicateKeyUpdateAssignments = append(insert.onDuplicateKeyUpdateAssignments, assignment{
field: field,
value: value,
})
return insert
}
func (s *insertStatus) GetSQL() (string, error) {
var fields []Field
var values []interface{}
if len(s.models) > 0 {
fields = s.models[0].GetTable().GetFields()
for _, model := range s.models {
values = append(values, model.GetValues())
}
} else {
fields = s.fields
values = s.values
}
sqlString := getCallerInfo() + "INSERT INTO " + (*s.table).GetSQL() + " (" + commaFields(fields) + ") VALUES " + commaValues(values)
if len(s.onDuplicateKeyUpdateAssignments) > 0 {
sqlString += " ON DUPLICATE KEY UPDATE " + commaAssignments(s.onDuplicateKeyUpdateAssignments)
}
return sqlString, nil
}
func (s *insertStatus) Execute() (result sql.Result, err error) {
sqlString, err := s.GetSQL()
if err != nil {
return nil, err
}
return s.database.Execute(sqlString)
}

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

18
order.go Normal file
View File

@ -0,0 +1,18 @@
package sqlingo
type OrderBy interface {
GetSQL() string
}
type orderBy struct {
by Expression
desc bool
}
func (o *orderBy) GetSQL() string {
sql := o.by.GetSQL()
if o.desc {
sql += " DESC"
}
return sql
}

250
select.go Normal file
View File

@ -0,0 +1,250 @@
package sqlingo
import (
"reflect"
"strconv"
)
type Select interface {
GetFields() []Field
GetSQL() string
Fetch(out ...interface{}) error
FetchCursor() (Cursor, error)
}
type SelectWithFields interface {
Select
From(tables ...Table) SelectWithTables
}
type SelectWithTables interface {
Select
SelectOrderBy
Where(conditions ...BooleanExpression) SelectWithWhere
GroupBy(expressions ...Expression) SelectWithGroupBy
}
type SelectWithWhere interface {
Select
SelectOrderBy
GroupBy(expressions ...Expression) SelectWithGroupBy
}
type SelectWithGroupBy interface {
Select
Having(conditions ...BooleanExpression) SelectWithGroupByHaving
OrderBy(orderBys ...OrderBy) SelectWithOrder
}
type SelectWithGroupByHaving interface {
SelectWithOrder
OrderBy(orderBys ...OrderBy) SelectWithOrder
}
type SelectOrderBy interface {
OrderBy(orderBys ...OrderBy) SelectWithOrder
}
type SelectWithOrder interface {
Limit(limit int) SelectWithLimit
Fetch(out ...interface{}) error
FetchCursor() (Cursor, error)
}
type SelectWithLimit interface {
Offset(offset int) SelectWithOffset
Fetch(out ...interface{}) error
FetchCursor() (Cursor, error)
}
type SelectWithOffset interface {
Fetch(out ...interface{}) error
FetchCursor() (Cursor, error)
}
type selectStatus struct {
database *Database
fields []Field
tables []*Table
where *BooleanExpression
orderBys []OrderBy
groupBys []Expression
having *BooleanExpression
limit *int
offset *int
lock string
}
func (s *selectStatus) copy() *selectStatus {
select_ := *s
select_.fields = s.GetFields()
return &select_
}
func (s *selectStatus) GetFields() []Field {
var fields []Field
fields = append(fields, s.fields...)
return fields
}
func (d *Database) Select(fields ... interface{}) SelectWithFields {
select_ := &selectStatus{database: d}
for _, field := range fields {
sql, priority := getSQLFromWhatever(field)
expression := &expression{sql: sql, priority: priority}
select_.fields = append(select_.fields, expression)
}
return select_
}
func (s *selectStatus) From(tables ...Table) SelectWithTables {
select_ := s.copy()
for _, table := range tables {
select_.tables = append(select_.tables, &table)
}
return select_
}
func (d *Database) SelectFrom(tables ...Table) SelectWithTables {
select_ := selectStatus{database: d}
for _, table := range tables {
select_.tables = append(select_.tables, &table)
fields := table.GetFields()
for _, field := range fields {
select_.fields = append(select_.fields, field)
}
}
return &select_
}
func (s *selectStatus) Where(conditions ...BooleanExpression) SelectWithWhere {
select_ := s.copy()
condition := And(conditions...)
select_.where = &condition
return select_
}
func (s *selectStatus) GroupBy(expressions ...Expression) SelectWithGroupBy {
select_ := s.copy()
select_.groupBys = expressions
return select_
}
func (s *selectStatus) Having(conditions ...BooleanExpression) SelectWithGroupByHaving {
select_ := s.copy()
condition := And(conditions...)
select_.having = &condition
return select_
}
func (s *selectStatus) OrderBy(orderBys ...OrderBy) SelectWithOrder {
select_ := s.copy()
select_.orderBys = append(select_.orderBys, orderBys...)
return select_
}
func (s *selectStatus) Limit(limit int) SelectWithLimit {
select_ := s.copy()
select_.limit = &limit
return select_
}
func (s *selectStatus) Offset(offset int) SelectWithOffset {
select_ := s.copy()
select_.limit = &offset
return select_
}
func (s *selectStatus) GetSQL() string {
sql := getCallerInfo() + "SELECT " + commaFields(s.fields)
if len(s.tables) > 0 {
var values []interface{}
for _, table := range s.tables {
values = append(values, table)
}
sql += " FROM " + commaValues(values)
}
if s.where != nil {
sql += " WHERE " + (*s.where).GetSQL()
}
if len(s.groupBys) != 0 {
sql += " GROUP BY " + commaExpressions(s.groupBys)
if s.having != nil {
sql += " HAVING " + (*s.having).GetSQL()
}
}
if len(s.orderBys) > 0 {
sql += " ORDER BY " + commaOrderBys(s.orderBys)
}
if s.limit != nil {
sql += " LIMIT " + strconv.Itoa(*s.limit)
}
if s.offset != nil {
sql += " OFFSET " + strconv.Itoa(*s.offset)
}
sql += s.lock
return sql
}
func (s *selectStatus) FetchCursor() (Cursor, error) {
sqlString := s.GetSQL()
cursor, err := s.database.Query(sqlString)
if err != nil {
return nil, err
}
return cursor, nil
}
func (s *selectStatus) Fetch(dest ...interface{}) error {
if len(dest) == 1 {
if reflect.ValueOf(dest[0]).Kind() == reflect.Ptr {
val := reflect.Indirect(reflect.ValueOf(dest[0]))
if val.Kind() == reflect.Slice {
cursor, err := s.FetchCursor()
if err != nil {
return err
}
defer cursor.Close()
for cursor.Next() {
if err != nil {
return err
}
elem := reflect.New(val.Type().Elem())
row := elem.Interface()
cursor.Scan(row)
val.Set(reflect.Append(val, reflect.Indirect(elem)))
}
return nil
}
}
}
cursor, err := s.FetchCursor()
if err != nil {
return err
}
defer cursor.Close()
for cursor.Next() {
err = cursor.Scan(dest...)
if err != nil {
return err
}
break
}
return nil
}

26
sqlingo-gen/main.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/lqs/sqlingo"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Printf("Usage: %s username:password@/database\n", os.Args[0])
return
}
dataSourceName := os.Args[1]
code, err := sqlingo.Generate("mysql", dataSourceName, nil)
if err != nil {
panic(err)
}
fmt.Print(code)
}

29
table.go Normal file
View File

@ -0,0 +1,29 @@
package sqlingo
type Table interface {
GetName() string
GetSQL() string
GetFields() []Field
}
type table struct {
Table
name string
sql string
}
func (t *table) GetName() string {
return t.name
}
func (t *table) GetSQL() string {
return t.sql
}
func (t *table) getOperatorPriority() int {
return 0
}
func NewTable(name string) Table {
return &table{name: name, sql: getSQLForName(name)}
}

80
update.go Normal file
View File

@ -0,0 +1,80 @@
package sqlingo
import (
"database/sql"
)
type updateStatus struct {
database *Database
table *Table
assignments []assignment
where *BooleanExpression
}
func (s *updateStatus) copy() *updateStatus {
update := *s
update.assignments = append([]assignment{}, s.assignments...)
return &update
}
func (d *Database) Update(table Table) UpdateWithTable {
return &updateStatus{database: d, table: &table}
}
type UpdateWithTable interface {
Set(field Field, value interface{}) UpdateWithSet
SetMap(map[Field]interface{}) UpdateWithSet
}
type UpdateWithSet interface {
Set(Field Field, value interface{}) UpdateWithSet
Where(conditions ...BooleanExpression) UpdateWithWhere
}
type UpdateWithWhere interface {
GetSQL() (string, error)
Execute() (sql.Result, error)
}
func (s *updateStatus) Set(field Field, value interface{}) UpdateWithSet {
update := s.copy()
update.assignments = append(update.assignments, assignment{
field: field,
value: value,
})
return update
}
func (s *updateStatus) SetMap(values map[Field]interface{}) UpdateWithSet {
update := s.copy()
for field, value := range values {
update.assignments = append(update.assignments, assignment{
field: field,
value: value,
})
}
return update
}
func (s *updateStatus) Where(conditions ...BooleanExpression) UpdateWithWhere {
update := s.copy()
condition := And(conditions...)
update.where = &condition
return update
}
func (s *updateStatus) GetSQL() (string, error) {
sqlString := getCallerInfo() + "UPDATE " + (*s.table).GetSQL() +
" SET " + commaAssignments(s.assignments) +
" WHERE " + (*s.where).GetSQL()
return sqlString, nil
}
func (s *updateStatus) Execute() (sql.Result, error) {
sqlString, err := s.GetSQL()
if err != nil {
return nil, err
}
return s.database.Execute(sqlString)
}