mirror of
https://github.com/chaisql/chai.git
synced 2025-09-27 03:55:59 +08:00
198 lines
3.6 KiB
Go
198 lines
3.6 KiB
Go
package statement
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/chaisql/chai/internal/database"
|
|
"github.com/chaisql/chai/internal/environment"
|
|
"github.com/chaisql/chai/internal/expr"
|
|
"github.com/cockroachdb/errors"
|
|
)
|
|
|
|
// A Statement represents a unique action that can be executed against the database.
|
|
type Statement interface {
|
|
Run(*Context) (*Result, error)
|
|
}
|
|
|
|
// Optional interface that allows a statement to specify if it is read-only.
|
|
// Defaults to false if not implemented.
|
|
type ReadOnly interface {
|
|
IsReadOnly() bool
|
|
}
|
|
|
|
// Optional interface that allows a statement to specify if they need a transaction.
|
|
// Defaults to true if not implemented.
|
|
// If true, the engine will auto-commit.
|
|
type Transactional interface {
|
|
NeedsTransaction() bool
|
|
}
|
|
|
|
// Optional interface that allows a statement to specify if they need to be bound to database
|
|
// objects.
|
|
type Bindable interface {
|
|
Bind(*Context) error
|
|
}
|
|
|
|
type Context struct {
|
|
DB *database.Database
|
|
Conn *database.Connection
|
|
Params []environment.Param
|
|
}
|
|
|
|
type Preparer interface {
|
|
Prepare(*Context) (Statement, error)
|
|
}
|
|
|
|
// Result of a query.
|
|
type Result struct {
|
|
Result database.Result
|
|
Tx *database.Transaction
|
|
closed bool
|
|
err error
|
|
}
|
|
|
|
func (r *Result) Iterator() (database.Iterator, error) {
|
|
if r.Result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return r.Result.Iterator()
|
|
}
|
|
|
|
func (r *Result) Iterate(fn func(r database.Row) error) (err error) {
|
|
if r.Result == nil {
|
|
return nil
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
r.err = err
|
|
}
|
|
}()
|
|
|
|
it, err := r.Result.Iterator()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer it.Close()
|
|
|
|
for it.Next() {
|
|
rr, err := it.Row()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := fn(rr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return it.Error()
|
|
}
|
|
|
|
// Skip iterates over the result and skips all rows.
|
|
// It is useful when you need the query to be executed
|
|
// but don't care about the results.
|
|
func (r *Result) Skip(ctx context.Context) (err error) {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
if r.Result == nil {
|
|
return nil
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
r.err = err
|
|
}
|
|
}()
|
|
|
|
it, err := r.Result.Iterator()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer it.Close()
|
|
|
|
for it.Next() {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return it.Error()
|
|
}
|
|
|
|
func (r *Result) Columns() ([]string, error) {
|
|
if r.Result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
stmt, ok := r.Result.(*StreamStmtResult)
|
|
if !ok || stmt.Stream.Op == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
env := environment.New(stmt.Context.DB, stmt.Context.Conn.GetTx(), stmt.Context.Params, nil)
|
|
return stmt.Stream.Columns(env)
|
|
}
|
|
|
|
// Close the result stream.
|
|
// After closing the result, Stream is not supposed to be used.
|
|
// If the result stream was already closed, it returns an error.
|
|
func (r *Result) Close() (err error) {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
|
|
if r.closed {
|
|
return errors.New("result already closed")
|
|
}
|
|
|
|
r.closed = true
|
|
|
|
if r.Tx != nil {
|
|
if r.Tx.Writable && r.err == nil {
|
|
err = r.Tx.Commit()
|
|
} else {
|
|
err = r.Tx.Rollback()
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func BindExpr(ctx *Context, tableName string, e expr.Expr) (err error) {
|
|
if e == nil {
|
|
return nil
|
|
}
|
|
|
|
var info *database.TableInfo
|
|
if tableName != "" {
|
|
info, err = ctx.Conn.GetTx().Catalog.GetTableInfo(tableName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
expr.Walk(e, func(e expr.Expr) bool {
|
|
switch t := e.(type) {
|
|
case *expr.Column:
|
|
if t == nil {
|
|
return true
|
|
}
|
|
|
|
if info == nil {
|
|
err = errors.New("no table specified")
|
|
return false
|
|
}
|
|
|
|
cc := info.ColumnConstraints.GetColumnConstraint(t.Name)
|
|
if cc == nil {
|
|
err = errors.Newf("column %s does not exist", t)
|
|
return false
|
|
}
|
|
t.Table = tableName
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return err
|
|
}
|