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

add more pg functions and extract() function #584

Merged
merged 6 commits into from
Aug 8, 2024
Merged
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
6 changes: 6 additions & 0 deletions postgres/messages/row_description.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ func VitessTypeToObjectID(typ query.Type) (int32, error) {
return OidDate, nil
case query.Type_NULL_TYPE:
return OidText, nil // NULL is treated as TEXT on the wire
case query.Type_ENUM:
return OidText, nil // TODO: temporary solution until we support CREATE TYPE
Hydrocharged marked this conversation as resolved.
Show resolved Hide resolved
default:
return 0, fmt.Errorf("unsupported type: %s", typ)
}
Expand Down Expand Up @@ -329,6 +331,8 @@ func VitessFieldToDataTypeSize(field *query.Field) (int16, error) {
return 4, nil
case query.Type_NULL_TYPE:
return -1, nil // NULL is treated as TEXT on the wire
case query.Type_ENUM:
return -1, nil // TODO: temporary solution until we support CREATE TYPE
default:
return 0, fmt.Errorf("unsupported type returned from engine: %s", field.Type)
}
Expand Down Expand Up @@ -389,6 +393,8 @@ func VitessFieldToDataTypeModifier(field *query.Field) (int32, error) {
return -1, nil
case query.Type_NULL_TYPE:
return -1, nil // NULL is treated as TEXT on the wire
case query.Type_ENUM:
return -1, nil // TODO: temporary solution until we support CREATE TYPE
default:
return 0, fmt.Errorf("unsupported type returned from engine: %s", field.Type)
}
Expand Down
2 changes: 1 addition & 1 deletion server/ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func nodeExpr(node tree.Expr) (vitess.Expr, error) {
case tree.CastExplicit, tree.CastShort:
// Both of these are acceptable
case tree.CastPrepend:
return nil, fmt.Errorf("typed literals are not yet supported")
// used for typed literals
default:
return nil, fmt.Errorf("unknown cast syntax")
}
Expand Down
205 changes: 205 additions & 0 deletions server/functions/extract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package functions

import (
"fmt"
"math"
"strings"
"time"

"github.com/dolthub/go-mysql-server/sql"
"github.com/shopspring/decimal"
"gopkg.in/src-d/go-errors.v1"

"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)

// initExtract registers the functions to the catalog.
func initExtract() {
framework.RegisterFunction(extract_text_date)
framework.RegisterFunction(extract_text_time)
framework.RegisterFunction(extract_text_timetz)
framework.RegisterFunction(extract_text_timestamp)
framework.RegisterFunction(extract_text_timestamptz)
//framework.RegisterFunction(extract_text_interval)
}

var ErrUnitNotSupported = errors.NewKind("unit \"%s\" not supported for type %s")

// extract_text_date represents the PostgreSQL date/time function, taking {text, date}
var extract_text_date = framework.Function2{
Name: "extract",
Return: pgtypes.Numeric,
Parameters: [2]pgtypes.DoltgresType{pgtypes.Text, pgtypes.Date},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]pgtypes.DoltgresType, val1, val2 any) (any, error) {
field := val1.(string)
if strings.HasPrefix(strings.ToLower(field), "timezone") {
return nil, ErrUnitNotSupported.New(field, "date")
}
dateVal := val2.(time.Time)
return getFieldFromTimeVal(field, dateVal)
},
}

// extract_text_time represents the PostgreSQL date/time function, taking {text, time without time zone}
var extract_text_time = framework.Function2{
Name: "extract",
Return: pgtypes.Numeric,
Parameters: [2]pgtypes.DoltgresType{pgtypes.Text, pgtypes.Time},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]pgtypes.DoltgresType, val1, val2 any) (any, error) {
field := val1.(string)
if strings.HasPrefix(strings.ToLower(field), "timezone") {
return nil, ErrUnitNotSupported.New(field, "time without time zone")
}
timeVal := val2.(time.Time)
return getFieldFromTimeVal(field, timeVal)
},
}

// extract_text_timetz represents the PostgreSQL date/time function, taking {text, time with time zone}
var extract_text_timetz = framework.Function2{
Name: "extract",
Return: pgtypes.Numeric,
Parameters: [2]pgtypes.DoltgresType{pgtypes.Text, pgtypes.TimeTZ},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]pgtypes.DoltgresType, val1, val2 any) (any, error) {
field := val1.(string)
timetzVal := val2.(time.Time)
return getFieldFromTimeVal(field, timetzVal)
},
}

// extract_text_timestamp represents the PostgreSQL date/time function, taking {text, timestamp without time zone}
var extract_text_timestamp = framework.Function2{
Name: "extract",
Return: pgtypes.Numeric,
Parameters: [2]pgtypes.DoltgresType{pgtypes.Text, pgtypes.Timestamp},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]pgtypes.DoltgresType, val1, val2 any) (any, error) {
field := val1.(string)
if strings.HasPrefix(strings.ToLower(field), "timezone") {
return nil, ErrUnitNotSupported.New(field, "timestamp without time zone")
}
tsVal := val2.(time.Time)
return getFieldFromTimeVal(field, tsVal)
},
}

// extract_text_timestamptz represents the PostgreSQL date/time function, taking {text, timestamp with time zone}
var extract_text_timestamptz = framework.Function2{
Name: "extract",
Return: pgtypes.Numeric,
Parameters: [2]pgtypes.DoltgresType{pgtypes.Text, pgtypes.TimestampTZ},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [3]pgtypes.DoltgresType, val1, val2 any) (any, error) {
field := val1.(string)
tstzVal := val2.(time.Time)
return getFieldFromTimeVal(field, tstzVal)
},
}

//// extract_text_interval represents the PostgreSQL date/time function, taking {text, interval}
//var extract_text_interval = framework.Function2{
// Name: "extract",
// Return: pgtypes.Numeric,
// Parameters: [2]pgtypes.DoltgresType{pgtypes.Text, pgtypes.Interval},
// IsNonDeterministic: true,
// Strict: true,
// Callable: func(ctx *sql.Context, _ [3]pgtypes.DoltgresType, val1, val2 any) (any, error) {
// field := val1.(string)
// if strings.HasPrefix(strings.ToLower(field), "timezone") {
// return nil, ErrUnitNotSupported.New(field, "time without time zone")
// }
// //intervalVal :=
// return nil, nil
// },
//}

// getFieldFromTimeVal returns the value for given field extracted from non-interval values.
func getFieldFromTimeVal(field string, tVal time.Time) (decimal.Decimal, error) {
switch strings.ToLower(field) {
case "century", "centuries":
return decimal.NewFromFloat(math.Ceil(float64(tVal.Year()) / 100)), nil
case "day", "days":
return decimal.NewFromInt(int64(tVal.Day())), nil
case "decade", "decades":
return decimal.NewFromFloat(math.Floor(float64(tVal.Year()) / 10)), nil
case "dow":
return decimal.NewFromInt(int64(tVal.Weekday())), nil
case "doy":
return decimal.NewFromInt(int64(tVal.YearDay())), nil
case "epoch":
return decimal.NewFromFloat(float64(tVal.UnixMicro()) / 1000000), nil
case "hour", "hours":
return decimal.NewFromInt(int64(tVal.Hour())), nil
case "isodow":
wd := int64(tVal.Weekday())
if wd == 0 {
wd = 7
}
return decimal.NewFromInt(wd), nil
case "isoyear":
year, _ := tVal.ISOWeek()
return decimal.NewFromInt(int64(year)), nil
case "julian":
return decimal.Decimal{}, fmt.Errorf("'julian' field extraction not supported yet")
case "microsecond", "microseconds":
w := float64(tVal.Second() * 1000000)
f := float64(tVal.Nanosecond()) / float64(1000)
return decimal.NewFromFloat(w + f), nil
case "millennium", "millenniums":
return decimal.NewFromFloat(math.Ceil(float64(tVal.Year()) / 1000)), nil
case "millisecond", "milliseconds":
w := float64(tVal.Second() * 1000)
f := float64(tVal.Nanosecond()) / float64(1000000)
return decimal.NewFromFloatWithExponent(w+f, -3), nil
case "minute", "minutes":
return decimal.NewFromInt(int64(tVal.Minute())), nil
case "month", "months":
return decimal.NewFromInt(int64(tVal.Month())), nil
case "quarter":
q := (int(tVal.Month())-1)/3 + 1
return decimal.NewFromInt(int64(q)), nil
case "second", "seconds":
w := float64(tVal.Second())
f := float64(tVal.Nanosecond()) / float64(1000000000)
return decimal.NewFromFloatWithExponent(w+f, -6), nil
case "timezone":
// TODO: postgres seem to use server timezone regardless of input value
return decimal.NewFromInt(-28800), nil
case "timezone_hour":
// TODO: postgres seem to use server timezone regardless of input value
return decimal.NewFromInt(-8), nil
case "timezone_minute":
// TODO: postgres seem to use server timezone regardless of input value
return decimal.NewFromInt(0), nil
case "week":
_, week := tVal.ISOWeek()
return decimal.NewFromInt(int64(week)), nil
case "year", "years":
return decimal.NewFromInt(int64(tVal.Year())), nil
default:
return decimal.Decimal{}, fmt.Errorf("unknown field given: %s", field)
}
}
4 changes: 4 additions & 0 deletions server/functions/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func Init() {
initDiv()
initDoltProcedures()
initExp()
initExtract()
initFactorial()
initFloor()
initFormatType()
Expand All @@ -74,10 +75,13 @@ func Init() {
initPgFunctionIsVisible()
initPgGetConstraintdef()
initPgGetExpr()
initPgGetFunctionIdentityArguments()
initPgGetFunctionDef()
initPgGetPartKeyDef()
initPgGetTriggerDef()
initPgGetUserbyid()
initPgGetViewDef()
initPgPostmasterStartTime()
initPgTableIsVisible()
initPi()
initPower()
Expand Down
50 changes: 50 additions & 0 deletions server/functions/pg_get_function_identity_arguments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package functions

import (
"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
"github.com/dolthub/doltgresql/server/types/oid"
)

// initPgGetFunctionIdentityArguments registers the functions to the catalog.
func initPgGetFunctionIdentityArguments() {
framework.RegisterFunction(pg_get_function_identity_arguments_oid)
}

// pg_get_function_identity_arguments_oid represents the PostgreSQL system information function.
var pg_get_function_identity_arguments_oid = framework.Function1{
Name: "pg_get_function_identity_arguments",
Return: pgtypes.Text,
Parameters: [1]pgtypes.DoltgresType{pgtypes.Oid},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [2]pgtypes.DoltgresType, val any) (any, error) {
oidVal := val.(uint32)
err := oid.RunCallback(ctx, oidVal, oid.Callbacks{
Function: func(ctx *sql.Context, function oid.ItemFunction) (cont bool, err error) {
// TODO: sql.Function does not have sufficient information about its arguments
return false, nil
},
})
if err != nil {
return "", err
}
return "", nil
},
}
49 changes: 49 additions & 0 deletions server/functions/pg_get_partkeydef.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package functions

import (
"github.com/dolthub/go-mysql-server/sql"

"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
"github.com/dolthub/doltgresql/server/types/oid"
)

// initPgGetPartKeyDef registers the functions to the catalog.
func initPgGetPartKeyDef() {
framework.RegisterFunction(pg_get_partkeydef_oid)
}

// pg_get_partkeydef_oid represents the PostgreSQL system catalog information function.
var pg_get_partkeydef_oid = framework.Function1{
Name: "pg_get_partkeydef",
Return: pgtypes.Text,
Parameters: [1]pgtypes.DoltgresType{pgtypes.Oid},
IsNonDeterministic: true,
Strict: true,
Callable: func(ctx *sql.Context, _ [2]pgtypes.DoltgresType, val any) (any, error) {
err := oid.RunCallback(ctx, val.(uint32), oid.Callbacks{
Table: func(ctx *sql.Context, schema oid.ItemSchema, table oid.ItemTable) (cont bool, err error) {
// TODO: sql.Table does not have sufficient information about partition
return false, nil
},
})
if err != nil {
return "", err
}
return "", nil
},
}
Loading
Loading