mirror of
https://github.com/chaisql/chai.git
synced 2025-09-26 19:51:21 +08:00
197 lines
4.0 KiB
Go
197 lines
4.0 KiB
Go
package table
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/chaisql/chai/internal/database"
|
|
"github.com/chaisql/chai/internal/environment"
|
|
"github.com/chaisql/chai/internal/stream"
|
|
"github.com/chaisql/chai/internal/tree"
|
|
"github.com/cockroachdb/errors"
|
|
)
|
|
|
|
// GenerateKeyOperator
|
|
type GenerateKeyOperator struct {
|
|
stream.BaseOperator
|
|
|
|
TableName string
|
|
OnConflict *stream.Stream
|
|
OnConflictDoNothing bool
|
|
}
|
|
|
|
func GenerateKey(tableName string) *GenerateKeyOperator {
|
|
return &GenerateKeyOperator{
|
|
TableName: tableName,
|
|
}
|
|
}
|
|
|
|
func GenerateKeyOnConflict(tableName string, onConflict *stream.Stream) *GenerateKeyOperator {
|
|
return &GenerateKeyOperator{
|
|
TableName: tableName,
|
|
OnConflict: onConflict,
|
|
}
|
|
}
|
|
|
|
func GenerateKeyOnConflictDoNothing(tableName string) *GenerateKeyOperator {
|
|
return &GenerateKeyOperator{
|
|
TableName: tableName,
|
|
OnConflictDoNothing: true,
|
|
}
|
|
}
|
|
|
|
func (op *GenerateKeyOperator) Iterator(in *environment.Environment) (stream.Iterator, error) {
|
|
tx := in.GetTx()
|
|
|
|
table, err := tx.Catalog.GetTable(tx, op.TableName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if table.Info.ReadOnly {
|
|
return nil, errors.New("cannot write to read-only table")
|
|
}
|
|
|
|
cols, err := op.Columns(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prev, err := op.Prev.Iterator(in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &GenerateKeyIterator{
|
|
Iterator: prev,
|
|
tableName: op.TableName,
|
|
env: in,
|
|
tx: tx,
|
|
table: table,
|
|
columns: cols,
|
|
onConflict: op.OnConflict,
|
|
onConflictDoNothing: op.OnConflictDoNothing,
|
|
}, nil
|
|
}
|
|
|
|
func (op *GenerateKeyOperator) String() string {
|
|
if op.OnConflictDoNothing {
|
|
return fmt.Sprintf("table.GenerateKeyOnConflictDoNothing(%q)", op.TableName)
|
|
}
|
|
if op.OnConflict != nil {
|
|
return fmt.Sprintf("table.GenerateKeyOnConflict(%q, %v)", op.TableName, op.OnConflict)
|
|
}
|
|
|
|
return fmt.Sprintf("table.GenerateKey(%q)", op.TableName)
|
|
}
|
|
|
|
type GenerateKeyIterator struct {
|
|
stream.Iterator
|
|
|
|
tableName string
|
|
env *environment.Environment
|
|
tx *database.Transaction
|
|
table *database.Table
|
|
columns []string
|
|
onConflict *stream.Stream
|
|
onConflictDoNothing bool
|
|
|
|
buf []byte
|
|
br database.BasicRow
|
|
err error
|
|
}
|
|
|
|
func (it *GenerateKeyIterator) Next() bool {
|
|
for it.Iterator.Next() {
|
|
it.buf = it.buf[:0]
|
|
|
|
r, err := it.Iterator.Row()
|
|
if err != nil {
|
|
it.err = err
|
|
return false
|
|
}
|
|
|
|
k, err := it.generateKey(r)
|
|
if err != nil {
|
|
it.err = err
|
|
return false
|
|
}
|
|
if k == nil {
|
|
// if the key is nil, it means the row was not inserted due to a conflict,
|
|
// we must skip the row
|
|
continue
|
|
}
|
|
|
|
// reset the buffered row with the new key
|
|
it.br.ResetWith(it.tableName, k, r)
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (it *GenerateKeyIterator) Row() (database.Row, error) {
|
|
return &it.br, it.Error()
|
|
}
|
|
|
|
func (it *GenerateKeyIterator) Error() error {
|
|
if it.err != nil {
|
|
return it.err
|
|
}
|
|
|
|
return it.Iterator.Error()
|
|
}
|
|
|
|
func (it *GenerateKeyIterator) generateKey(r database.Row) (*tree.Key, error) {
|
|
k, err := it.table.GenerateKey(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check if the primary key already exists
|
|
exists, err := it.table.Exists(k)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
return k, nil
|
|
}
|
|
|
|
if it.onConflict == nil && !it.onConflictDoNothing {
|
|
return nil, &database.ConstraintViolationError{
|
|
Constraint: "PRIMARY KEY",
|
|
Columns: it.table.Info.PrimaryKey.Columns,
|
|
Key: k,
|
|
}
|
|
}
|
|
|
|
if it.onConflictDoNothing {
|
|
// return nil key to signal there was a conflict
|
|
return nil, nil
|
|
}
|
|
|
|
it.br.ResetWith(it.tableName, k, r)
|
|
|
|
// execute the onConflict stream
|
|
stream.InsertBefore(it.onConflict.Op, stream.Rows(it.columns, &it.br))
|
|
|
|
newIt, err := it.onConflict.Iterator(it.env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for newIt.Next() {
|
|
}
|
|
if err := newIt.Error(); err != nil {
|
|
_ = newIt.Close()
|
|
return nil, err
|
|
}
|
|
|
|
err = newIt.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// return nil key to signal there was a conflict
|
|
return nil, nil
|
|
}
|