Skip to content

Commit

Permalink
Merge pull request #57 from xushiwei/db
Browse files Browse the repository at this point in the history
table statement: new spec
  • Loading branch information
xushiwei authored Feb 5, 2024
2 parents b22ad40 + 6770e30 commit 95dfde2
Show file tree
Hide file tree
Showing 7 changed files with 456 additions and 343 deletions.
161 changes: 142 additions & 19 deletions ydb/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,70 +18,192 @@ package ydb

import (
"context"
"database/sql"
"errors"
"log"
"reflect"

"github.com/goplus/gop/ast"
)

var (
ErrDuplicated = errors.New("duplicated")
)

// -----------------------------------------------------------------------------

type Class struct {
name string
tbl string
apis map[string]*api
name string
tbl string
sql *Sql
db *sql.DB
apis map[string]*api

query *query // query

api *api
result []reflect.Value
ret func(args ...any)
result []reflect.Value // result of an api call

ret func(args ...any)
}

func newClass(name string) *Class {
apis := make(map[string]*api)
return &Class{name: name, apis: apis}
func newClass(name string, sql *Sql) *Class {
if sql.db == nil {
log.Panicln("please call `engine <sqlDriverName>` first")
}
return &Class{
name: name,
sql: sql,
db: sql.db,
apis: make(map[string]*api),
}
}

func (p *Class) create(ctx context.Context, sql *Sql) {
func (p *Class) gen(ctx context.Context) {
}

// Use sets the default table used in following sql operations.
func (p *Class) Use(table string, src ...ast.Node) {
if _, ok := p.sql.tables[table]; !ok {
log.Panicln("table not found:", table)
}
p.tbl = table
}

// Ret checks a query or call result.
//
// For checking query result:
// - ret <colName1>, &<var1>, <colName2>, &<var2>, ...
// - ret <colName1>, &<varSlice1>, <colName2>, &<varSlice2>, ...
// - ret &<structVar>
// - ret &<structSlice>
//
// For checking call result:
// - TODO
func (p *Class) Ret__0(src ast.Node, args ...any) {
if p.ret == nil {
log.Panicln("please call `ret` after a `query` or `call` statement")
}
p.ret(args...)
}

// Ret checks a query or call result.
func (p *Class) Ret__1(args ...any) {
p.ret(args...)
p.Ret__0(nil, args...)
}

// -----------------------------------------------------------------------------

// Use sets the default table used in following sql operations.
func (p *Class) Use(table string, src ...ast.Node) {
p.tbl = table
}

// Insert inserts a new row.
// - insert <colName1>, <val1>, <colName2>, <val2>, ...
// - insert <structValOrPtr>
// - insert <structSlice>
func (p *Class) Insert__0(src ast.Node, kvPair ...any) {

}

// Insert inserts a new row.
func (p *Class) Insert__1(kvPair ...any) {
p.Insert__0(nil, kvPair...)
}

// Count returns rows of a query result.
func (p *Class) Count__0(src ast.Node, cond string, args ...any) (n int) {
if p.tbl == "" {
log.Panicln("please call `use <tableName>` to specified a table name")
}
row := p.db.QueryRowContext(context.TODO(), "SELECT COUNT(*) FROM "+p.tbl+" WHERE "+cond, args...)
if err := row.Scan(&n); err != nil {
log.Panicln("count:", err)
}
return
}

// Count returns rows of a query result.
func (p *Class) Count__1(cond string, args ...any) (n int) {
return p.Count__0(nil, cond, args...)
}

// -----------------------------------------------------------------------------

type query struct {
cond string // where
args []any // one of query argument <argN> can be a slice
limit int // 0 means no limit
}

// For checking query result:
// - ret <colName1>, &<var1>, <colName2>, &<var2>, ...
// - ret <colName1>, &<varSlice1>, <colName2>, &<varSlice2>, ...
// - ret &<structVar>
// - ret &<structSlice>
func (p *Class) queryRet(args ...any) {
nArg := len(args)
if nArg == 1 {
p.queryRetPtr(args[0])
} else {
p.queryRetKvPair(args...)
}
p.query = nil
p.ret = nil
}

func (p *Class) queryRet(kvPair ...any) {
// For checking query result:
// - ret &<structVar>
// - ret &<structSlice>
func (p *Class) queryRetPtr(arg any) {
}

// For checking query result:
// - ret <colName1>, &<var1>, <colName2>, &<var2>, ...
// - ret <colName1>, &<varSlice1>, <colName2>, &<varSlice2>, ...
func (p *Class) queryRetKvPair(kvPair ...any) {
nPair := len(kvPair)
if nPair < 2 || nPair&1 != 0 {
log.Panicln("usage: ret <colName1> &<var1> <colName2> &<var2> ...")
}
n := nPair >> 1
names := make([]string, n)
rets := make([]any, n)
for i := 0; i < nPair; i += 2 {
names[i>>1] = kvPair[i].(string)
rets[i>>1] = kvPair[i+1]
}
}

// Query creates a new query.
func (p *Class) Query(query string, src ...ast.Node) {
// - query <cond>, <arg1>, <arg2>, ...
func (p *Class) Query__0(src ast.Node, cond string, args ...any) {
p.query = &query{
cond: cond, args: args,
}
p.ret = p.queryRet
}

// Query creates a new query.
func (p *Class) Query__1(cond string, args ...any) {
p.Query__0(nil, cond, args...)
}

// Limit sets query result rows limit.
func (p *Class) Limit__0(n int, src ...ast.Node) {
if p.query == nil {
log.Panicln("please call `limit` after a query statement")
}
p.query.limit = n
}

// Limit checks if query result rows is < n or not.
func (p *Class) Limit__1(src ast.Node, n int, cond string, args ...any) {
ret := p.Count__0(src, cond, args...)
if ret >= n {
log.Panicf("limit %s: got %d, expected <%d\n", cond, ret, n)
}
}

// Limit checks if query result rows is < n or not.
func (p *Class) Limit__1(n int, query string, src ...ast.Node) {
func (p *Class) Limit__2(n int, cond string, args ...any) {
p.Limit__1(nil, n, cond, args...)
}

// -----------------------------------------------------------------------------
Expand All @@ -101,7 +223,7 @@ func (p *Class) Api(name string, spec any, src ...*ast.FuncLit) {
// Call calls an api with specified args.
func (p *Class) Call__0(src ast.Node, args ...any) {
if p.api == nil {
log.Panicln("please call after an api definition")
log.Panicln("please call `call` after an api definition")
}
vArgs := make([]reflect.Value, len(args))
for i, arg := range args {
Expand All @@ -117,6 +239,7 @@ func (p *Class) Call__1(args ...any) {
}

func (p *Class) callRet(args ...any) {
p.ret = nil
}

// -----------------------------------------------------------------------------
39 changes: 29 additions & 10 deletions ydb/classfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"database/sql"
"log"
"reflect"
"strings"

"github.com/goplus/gop/ast"
Expand Down Expand Up @@ -65,28 +66,46 @@ func (p *Sql) Engine__1() string {
return p.driver
}

// Table creates a new table by a spec.
func (p *Sql) Table(nameVer string, spec func(), src ...ast.Node) {
func (p *Sql) defineTable(nameVer string, zeroSchema any) {
var name, ver string
pos := strings.IndexByte(nameVer, ' ') // user v0.1.0
if pos < 0 {
log.Panicln("table name should have a version: eg. `user v0.1.0`")
ver = nameVer
} else {
name, ver = nameVer[:pos], strings.TrimLeft(nameVer[pos+1:], " \t")
}
name, ver := nameVer[:pos], strings.TrimLeft(nameVer[pos+1:], " \t")
tbl := newTable(name, ver)
if _, ok := p.tables[name]; ok {
log.Panicf("table `%s` exists\n", name)
}
schema := reflect.TypeOf(zeroSchema).Elem()
if name == "" {
name = schema.Name()
}
tbl := newTable(name, ver, schema)
tbl.create(context.TODO(), p)
p.dbTable = tbl
p.tables[name] = tbl
spec()
tbl.create(context.TODO(), p)
p.dbTable = nil
}

// Table creates a new table by specified Schema.
func Gopt_Sql_Gopx_Table[Schema any](sql interface{ defineTable(string, any) }, nameVer string, src ...ast.Node) {
sql.defineTable(nameVer, (*Schema)(nil))
}

// From migrates from old table because it's an incompatible change
func (p *Sql) From(old string, migrate func(), src ...ast.Node) {
if p.dbTable == nil {
log.Panicln("please call `from` after a `table` statement")
}
}

// Class creates a new class by a spec.
func (p *Sql) Class(name string, spec func(), src ...ast.Node) {
cls := newClass(name)
cls := newClass(name, p)
p.dbClass = cls
p.classes[name] = cls
spec()
cls.create(context.TODO(), p)
cls.gen(context.TODO())
p.dbClass = nil
}

Expand Down
Loading

0 comments on commit 95dfde2

Please sign in to comment.