Skip to content

Commit

Permalink
feat: query
Browse files Browse the repository at this point in the history
  • Loading branch information
kj455 committed Aug 6, 2024
1 parent d3a11a0 commit f90fa1c
Show file tree
Hide file tree
Showing 13 changed files with 869 additions and 13 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ PKG := $(shell go list ./... | grep -v '/mock$$')
print-pkg:
@echo $(PKG)
test:
go test -v $(PKG)
gotestsum --format testname $(PKG)
testw:
gow test -timeout 5s $(PKG)
gotestsum --watch --format testname $(PKG)
lint:
golangci-lint run
clean:
Expand Down
104 changes: 104 additions & 0 deletions pkg/constant/constant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package constant

import "fmt"

type Kind string

const (
KIND_INT Kind = "int"
KIND_STR Kind = "string"
)

// Const denotes values stored in the database.
type Const struct {
val any
kind Kind
}

func NewConstant(kind Kind, val any) (*Const, error) {
switch kind {
case KIND_INT:
if _, ok := val.(int); !ok {
return nil, fmt.Errorf("constant: value is not an integer")
}
case KIND_STR:
if _, ok := val.(string); !ok {
return nil, fmt.Errorf("constant: value is not a string")
}
default:
}
return &Const{
val: val,
kind: kind,
}, nil
}

// AsInt returns the integer value of the constant.
func (c *Const) AsInt() int {
if c.kind == KIND_INT {
return c.val.(int)
}
return 0 // or panic/error if you want to handle it strictly
}

// AsString returns the string value of the constant.
func (c *Const) AsString() string {
if c.kind == KIND_STR {
return c.val.(string)
}
return "" // or panic/error if you want to handle it strictly
}

// Equals checks if two constants are equal.
func (c *Const) Equals(other *Const) bool {
if c.kind != other.kind {
return false
}
return c.val == other.val
}

// CompareTo returns 0 if two constants are equal, -1 if the receiver is less than the other, and 1 if the receiver is greater than the other.
func (c *Const) CompareTo(other *Const) int {
if c.kind != other.kind {
return 0 // or panic/error if you want to handle it strictly
}
if c.val == other.val {
return 0
}
// TODO: Implement comparison for other types
if c.val.(int) < other.val.(int) {
return -1
}
return 1
}

// HashCode returns the hash code of the constant.
func (c *Const) HashCode() int {
// TODO: Implement more valid hash code
switch c.kind {
case KIND_INT:
return c.AsInt()
case KIND_STR:
return len(c.AsString())
}
return 0
}

// ToString returns the string representation of the constant.
func (c *Const) ToString() string {
switch c.kind {
case KIND_INT:
return fmt.Sprint(c.AsInt())
case KIND_STR:
return c.AsString()
}
return ""
}

func (c *Const) Kind() Kind {
return c.kind
}

func (c *Const) AnyValue() any {
return c.val
}
37 changes: 37 additions & 0 deletions pkg/constant/constant_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package constant

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestConstant(t *testing.T) {
t.Run("NewConstant", func(t *testing.T) {
c, _ := NewConstant(KIND_INT, 42)
assert.Equal(t, 42, c.AsInt())
c, _ = NewConstant(KIND_STR, "hello")
assert.Equal(t, "hello", c.AsString())
_, err := NewConstant(KIND_INT, "hello")
assert.Error(t, err)
})
t.Run("Equals", func(t *testing.T) {
c1, _ := NewConstant(KIND_INT, 42)
c2, _ := NewConstant(KIND_INT, 42)
c3, _ := NewConstant(KIND_INT, 43)
c4, _ := NewConstant(KIND_STR, "hello")
assert.True(t, c1.Equals(c2))
assert.False(t, c1.Equals(c3))
assert.False(t, c1.Equals(c4))
})
t.Run("CompareTo", func(t *testing.T) {
c1, _ := NewConstant(KIND_INT, 42)
c2, _ := NewConstant(KIND_INT, 42)
c3, _ := NewConstant(KIND_INT, 43)
c4, _ := NewConstant(KIND_STR, "hello")
assert.Equal(t, 0, c1.CompareTo(c2))
assert.Equal(t, -1, c1.CompareTo(c3))
assert.Equal(t, 1, c3.CompareTo(c1))
assert.Equal(t, 0, c4.CompareTo(c4))
})
}
61 changes: 61 additions & 0 deletions pkg/query/expression.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package query

import (
"github.com/kj455/db/pkg/constant"
"github.com/kj455/db/pkg/record"
)

// Expression corresponds to SQL expressions.
type ExpressionImpl struct {
val *constant.Const
field string
}

// NewConstantExpression creates a new expression with a constant value.
func NewConstantExpression(val *constant.Const) Expression {
return &ExpressionImpl{val: val}
}

// NewFieldExpression creates a new expression with a field name.
func NewFieldExpression(field string) Expression {
return &ExpressionImpl{field: field}
}

// Evaluate evaluates the expression with respect to the current constant of the specified scan.
func (e *ExpressionImpl) Evaluate(s Scan) (*constant.Const, error) {
if e.val != nil {
return e.val, nil
}
return s.GetVal(e.field)
}

// IsFieldName returns true if the expression is a field reference.
func (e *ExpressionImpl) IsFieldName() bool {
return e.field != ""
}

// AsConstant returns the constant corresponding to a constant expression, or nil if the expression does not denote a constant.
func (e *ExpressionImpl) AsConstant() *constant.Const {
return e.val
}

// AsFieldName returns the field name corresponding to a constant expression, or an empty string if the expression does not denote a field.
func (e *ExpressionImpl) AsFieldName() string {
return e.field
}

// AppliesTo determines if all of the fields mentioned in this expression are contained in the specified schema.
func (e *ExpressionImpl) AppliesTo(sch record.Schema) bool {
if e.val != nil {
return true
}
return sch.HasField(e.field)
}

// ToString returns the string representation of the expression.
func (e *ExpressionImpl) ToString() string {
if e.val != nil {
return e.val.ToString()
}
return e.field
}
62 changes: 62 additions & 0 deletions pkg/query/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package query

import (
"github.com/kj455/db/pkg/constant"
"github.com/kj455/db/pkg/record"
)

type Scan interface {
// BeforeFirst positions the scan before its first record.
// A subsequent call to Next will return the first record.
BeforeFirst() error

// Next moves the scan to the next record.
// Returns false if there is no next record.
Next() bool

// GetInt returns the value of the specified integer field in the current record.
GetInt(field string) (int, error)

// GetString returns the value of the specified string field in the current record.
GetString(field string) (string, error)

// GetVal returns the value of the specified field in the current record, expressed as a Constant.
GetVal(field string) (*constant.Const, error)

// HasField checks if the scan has the specified field.
// The field parameter represents the name of the field.
// Returns true if the scan has that field.
HasField(field string) bool

// Close closes the scan and its subscans, if any.
Close()
}

type UpdateScan interface {
Scan
SetInt(field string, val int) error
SetString(field string, val string) error
SetVal(field string, val *constant.Const) error
Insert() error
Delete() error

GetRID() record.RID
MoveToRID(rid record.RID) error
}

type Predicate interface {
IsSatisfied(s Scan) (bool, error)
}

type Expression interface {
Evaluate(s Scan) (*constant.Const, error)
IsFieldName() bool
AsConstant() *constant.Const
AsFieldName() string
AppliesTo(sch record.Schema) bool
ToString() string
}

type PlanInfo interface {
DistinctValues(field string) int
}
Loading

0 comments on commit f90fa1c

Please sign in to comment.