Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0.10 sql #1070

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/common/display/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func Example_respTxQuery_json() {
// },
// "body": {
// "desc": "This is a test transaction for cli",
// "payload": "AAH4Vrg5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vyzs3MxoR0ZXh0gMSDZm9v",
// "payload": "AAH4V7g5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vyz87Nx4R0ZXh0gMDEg2Zvbw==",
// "type": "execute",
// "fee": "100",
// "nonce": 10,
Expand Down Expand Up @@ -194,7 +194,7 @@ func Example_respTxQuery_WithRaw_json() {
// },
// "body": {
// "desc": "This is a test transaction for cli",
// "payload": "AAH4Vrg5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vyzs3MxoR0ZXh0gMSDZm9v",
// "payload": "AAH4V7g5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vyz87Nx4R0ZXh0gMDEg2Zvbw==",
// "type": "execute",
// "fee": "100",
// "nonce": 10,
Expand All @@ -209,8 +209,8 @@ func Example_respTxQuery_WithRaw_json() {
// "gas_used": 10,
// "gas_wanted": 10
// },
// "raw": "0001f8eaf850b841cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e2534758008c736563703235366b315f6570f88ea25468697320697320612074657374207472616e73616374696f6e20666f7220636c69b85a0001f856b8397866363137616631636137373465626264366432336538666531326335366434316432356132326438316538386636376336633665653064348b6372656174655f75736572cecdccc6847465787480c483666f6f8765786563757465640a846173646686636f6e63617480",
// "warning": "HASH MISMATCH: requested 31303234; received f866b4251d21552de1bc5b819a4b563a540146954e956e8150163574ce5325ac"
// "raw": "0001f8ebf850b841cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e2534758008c736563703235366b315f6570f88fa25468697320697320612074657374207472616e73616374696f6e20666f7220636c69b85b0001f857b8397866363137616631636137373465626264366432336538666531326335366434316432356132326438316538386636376336633665653064348b6372656174655f75736572cfcecdc7847465787480c0c483666f6f8765786563757465640a846173646686636f6e63617480",
// "warning": "HASH MISMATCH: requested 31303234; received 3a05fc591b97c720a3c67852807f3df5a27ab51d41fca03b86c92af9a1915c4c"
// },
// "error": ""
// }
Expand All @@ -224,7 +224,7 @@ func Test_TxHashAndExecResponse(t *testing.T) {
Hash: hash,
QueryResp: &RespTxQuery{Msg: qr},
}
expectJson := `{"tx_hash":"0102030405","exec_result":{"hash":"0102030405","height":10,"tx":{"signature":{"sig":"yz/tf2/zblkFTASoMbIV5RQFJ1PuNT5v4x1LTvc2rNYVUSfbVV0wBroU/LTHm7rVbI5juBqYljGbsFOp4lNHWAA=","type":"secp256k1_ep"},"body":{"desc":"This is a test transaction for cli","payload":"AAH4Vrg5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vyzs3MxoR0ZXh0gMSDZm9v","type":"execute","fee":"100","nonce":10,"chain_id":"asdf"},"serialization":"concat","sender":""},"tx_result":{"code":0,"log":"This is log","gas_used":10,"gas_wanted":10}}}`
expectJson := "{\"tx_hash\":\"0102030405\",\"exec_result\":{\"hash\":\"0102030405\",\"height\":10,\"tx\":{\"signature\":{\"sig\":\"yz/tf2/zblkFTASoMbIV5RQFJ1PuNT5v4x1LTvc2rNYVUSfbVV0wBroU/LTHm7rVbI5juBqYljGbsFOp4lNHWAA=\",\"type\":\"secp256k1_ep\"},\"body\":{\"desc\":\"This is a test transaction for cli\",\"payload\":\"AAH4V7g5eGY2MTdhZjFjYTc3NGViYmQ2ZDIzZThmZTEyYzU2ZDQxZDI1YTIyZDgxZTg4ZjY3YzZjNmVlMGQ0i2NyZWF0ZV91c2Vyz87Nx4R0ZXh0gMDEg2Zvbw==\",\"type\":\"execute\",\"fee\":\"100\",\"nonce\":10,\"chain_id\":\"asdf\"},\"serialization\":\"concat\",\"sender\":\"\"},\"tx_result\":{\"code\":0,\"log\":\"This is log\",\"gas_used\":10,\"gas_wanted\":10}}}"
expectText := "TxHash: 0102030405\nStatus: success\nHeight: 10\nLog: This is log"

outText, err := resp.MarshalText()
Expand Down
14 changes: 14 additions & 0 deletions core/types/decimal/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,20 @@ func (d *Decimal) Float64() (float64, error) {
return d.dec.Float64()
}

// Copy returns a copy of the decimal.
func (d *Decimal) Copy() *Decimal {
return &Decimal{
dec: apd.Decimal{
Coeff: *apd.NewBigInt(d.dec.Coeff.Int64()),
Form: d.dec.Form,
Negative: d.dec.Negative,
Exponent: d.dec.Exponent,
},
scale: d.scale,
precision: d.precision,
}
}

var _ json.Marshaler = Decimal{}
var _ json.Marshaler = (*Decimal)(nil)

Expand Down
143 changes: 118 additions & 25 deletions core/types/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package types
import (
"errors"
"fmt"
"regexp"
"slices"
"strconv"
"strings"

"github.com/kwilteam/kwil-db/core/types/decimal"
Expand Down Expand Up @@ -1099,7 +1101,7 @@ type DataType struct {
// IsArray is true if the type is an array.
IsArray bool `json:"is_array"`
// Metadata is the metadata of the type.
Metadata [2]uint16 `json:"metadata"`
Metadata *[2]uint16 `json:"metadata" rlp:"optional"`
}

// String returns the string representation of the type.
Expand All @@ -1121,30 +1123,28 @@ func (c *DataType) String() string {
return str.String()
}

var ZeroMetadata = [2]uint16{}

// PGString returns the string representation of the type in Postgres.
func (c *DataType) PGString() (string, error) {
// PGScalar returns the scalar representation of the type in Postgres.
func (c *DataType) PGScalar() (string, error) {
var scalar string
switch strings.ToLower(c.Name) {
case intStr:
scalar = "INT8"
scalar = "int8"
case textStr:
scalar = "TEXT"
scalar = "text"
case boolStr:
scalar = "BOOL"
scalar = "bool"
case blobStr:
scalar = "BYTEA"
scalar = "bytea"
case uuidStr:
scalar = "UUID"
scalar = "uuid"
case uint256Str:
scalar = "UINT256"
scalar = "uint256"
case DecimalStr:
if c.Metadata == ZeroMetadata {
return "", fmt.Errorf("decimal type must have metadata")
if c.Metadata == nil {
scalar = "numeric"
} else {
scalar = fmt.Sprintf("numeric(%d,%d)", c.Metadata[0], c.Metadata[1])
}

scalar = fmt.Sprintf("NUMERIC(%d,%d)", c.Metadata[0], c.Metadata[1])
case nullStr:
return "", fmt.Errorf("cannot have null column type")
case unknownStr:
Expand All @@ -1153,6 +1153,16 @@ func (c *DataType) PGString() (string, error) {
return "", fmt.Errorf("unknown column type: %s", c.Name)
}

return scalar, nil
}

// PGString returns the string representation of the type in Postgres.
func (c *DataType) PGString() (string, error) {
scalar, err := c.PGScalar()
if err != nil {
return "", err
}

if c.IsArray {
return scalar + "[]", nil
}
Expand All @@ -1164,14 +1174,15 @@ func (c *DataType) Clean() error {
c.Name = strings.ToLower(c.Name)
switch c.Name {
case intStr, textStr, boolStr, blobStr, uuidStr, uint256Str: // ok
if c.Metadata != ZeroMetadata {
if c.Metadata != nil {
return fmt.Errorf("type %s cannot have metadata", c.Name)
}

return nil
case DecimalStr:
if c.Metadata == ZeroMetadata {
return fmt.Errorf("decimal type must have metadata")
if c.Metadata == nil {
// numeric can have unspecified precision and scale
return nil
}

err := decimal.CheckPrecisionAndScale(c.Metadata[0], c.Metadata[1])
Expand All @@ -1185,7 +1196,7 @@ func (c *DataType) Clean() error {
return fmt.Errorf("type %s cannot be an array", c.Name)
}

if c.Metadata != ZeroMetadata {
if c.Metadata != nil {
return fmt.Errorf("type %s cannot have metadata", c.Name)
}

Expand Down Expand Up @@ -1219,10 +1230,10 @@ func (c *DataType) EqualsStrict(other *DataType) bool {
return false
}

if (c.Metadata == ZeroMetadata) != (other.Metadata == ZeroMetadata) {
if (c.Metadata == nil) != (other.Metadata == nil) {
return false
}
if c.Metadata != ZeroMetadata {
if c.Metadata != nil {
if c.Metadata[0] != other.Metadata[0] || c.Metadata[1] != other.Metadata[1] {
return false
}
Expand Down Expand Up @@ -1275,8 +1286,8 @@ var (
// For type detection, users should prefer compare a datatype
// name with the DecimalStr constant.
DecimalType = &DataType{
Name: DecimalStr,
Metadata: [2]uint16{1, 0}, // the minimum precision and scale
Name: DecimalStr,
// unspecified precision and scale
}
DecimalArrayType = ArrayType(DecimalType)
Uint256Type = &DataType{
Expand All @@ -1292,6 +1303,11 @@ var (
UnknownType = &DataType{
Name: unknownStr,
}
// RecordType is a special type used internally to represent
// an record with referenceable fields.
RecordType = &DataType{
Name: "record",
}
)

// ArrayType creates an array type of the given type.
Expand All @@ -1309,7 +1325,7 @@ func ArrayType(t *DataType) *DataType {

const (
textStr = "text"
intStr = "int"
intStr = "int8"
boolStr = "bool"
blobStr = "blob"
uuidStr = "uuid"
Expand All @@ -1320,15 +1336,92 @@ const (
unknownStr = "unknown"
)

// maps type names to their base names
var typeAlias = map[string]string{
"string": textStr,
"text": textStr,
"int": intStr,
"integer": intStr,
"int8": intStr,
"bool": boolStr,
"boolean": boolStr,
"blob": blobStr,
"bytea": blobStr,
"uuid": uuidStr,
"decimal": DecimalStr,
"numeric": DecimalStr,
}

// NewDecimalType creates a new fixed point decimal type.
func NewDecimalType(precision, scale uint16) (*DataType, error) {
err := decimal.CheckPrecisionAndScale(precision, scale)
if err != nil {
return nil, err
}

met := [2]uint16{precision, scale}

return &DataType{
Name: DecimalStr,
Metadata: [2]uint16{precision, scale},
Metadata: &met,
}, nil
}

// ParseDataType parses a string into a data type.
func ParseDataType(s string) (*DataType, error) {
// four cases: TEXT, TEXT[], TEXT(1,2), TEXT(1,2)[]
// we will parse the type first, then the array, then the metadata
// we will not allow metadata without an array

s = strings.TrimSpace(strings.ToLower(s))
if s == "" {
return nil, errors.New("empty data type")
}

// Regular expression to parse the data type
re := regexp.MustCompile(`^([a-z0-9]+)(\(([\d, ]+)\))?(\[\])?$`)
matches := re.FindStringSubmatch(s)

if len(matches) == 0 {
return nil, fmt.Errorf("invalid data type format: %s", s)
}

baseType := matches[1]
rawMetadata := matches[3]
isArray := matches[4] == "[]"

var metadata *[2]uint16
if rawMetadata != "" {
metadata = &[2]uint16{}
// only decimal types can have metadata
if baseType != DecimalStr {
return nil, fmt.Errorf("metadata is only allowed for decimal type")
}

parts := strings.Split(rawMetadata, ",")
// can be either DECIMAL(10,5) or just DECIMAL
if len(parts) != 2 && len(parts) != 0 {
return nil, fmt.Errorf("invalid metadata format: %s", rawMetadata)
}
for i, part := range parts {
num, err := strconv.Atoi(strings.TrimSpace(part))
if err != nil {
return nil, fmt.Errorf("invalid metadata value: %s", part)
}
metadata[i] = uint16(num)
}
}

baseName, ok := typeAlias[baseType]
if !ok {
return nil, fmt.Errorf("unknown data type: %s", baseType)
}

dt := &DataType{
Name: baseName,
Metadata: metadata,
IsArray: isArray,
}

return dt, dt.Clean()
}
Loading