chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
// Copyright 2025 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 (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/apd/v3"
|
||||
cerrors "github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/duration"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/timeofday"
|
||||
"github.com/dolthub/doltgresql/postgres/parser/timetz"
|
||||
"github.com/dolthub/doltgresql/server/functions/framework"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// initDatePart registers the functions to the catalog.
|
||||
func initDatePart() {
|
||||
framework.RegisterFunction(date_part_text_date)
|
||||
framework.RegisterFunction(date_part_text_time)
|
||||
framework.RegisterFunction(date_part_text_timetz)
|
||||
framework.RegisterFunction(date_part_text_timestamp)
|
||||
framework.RegisterFunction(date_part_text_timestamptz)
|
||||
framework.RegisterFunction(date_part_text_interval)
|
||||
}
|
||||
|
||||
// date_part_text_date represents the PostgreSQL date_part function for date type.
|
||||
var date_part_text_date = framework.Function2{
|
||||
Name: "date_part",
|
||||
Return: pgtypes.Float64,
|
||||
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.Date},
|
||||
Strict: true,
|
||||
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
|
||||
field := val1.(string)
|
||||
dateVal := val2.(time.Time)
|
||||
switch strings.ToLower(field) {
|
||||
case "hour", "hours", "microsecond", "microseconds", "millisecond", "milliseconds",
|
||||
"minute", "minutes", "second", "seconds", "timezone", "timezone_hour", "timezone_minute":
|
||||
return nil, ErrUnitNotSupported.New(field, "date")
|
||||
case "epoch":
|
||||
return float64(dateVal.UnixMicro()) / 1000000, nil
|
||||
default:
|
||||
result, err := getFieldFromTimeVal(field, dateVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := result.Float64()
|
||||
return f, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// date_part_text_time represents the PostgreSQL date_part function for time type.
|
||||
var date_part_text_time = framework.Function2{
|
||||
Name: "date_part",
|
||||
Return: pgtypes.Float64,
|
||||
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.Time},
|
||||
Strict: true,
|
||||
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
|
||||
field := val1.(string)
|
||||
timeVal := val2.(timeofday.TimeOfDay).ToTime()
|
||||
switch strings.ToLower(field) {
|
||||
case "century", "centuries", "day", "days", "decade", "decades", "dow", "doy",
|
||||
"isodow", "isoyear", "julian", "millennium", "millenniums", "month", "months",
|
||||
"quarter", "timezone", "timezone_hour", "timezone_minute", "week", "year", "years":
|
||||
return nil, ErrUnitNotSupported.New(field, "time without time zone")
|
||||
default:
|
||||
result, err := getFieldFromTimeVal(field, timeVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := result.Float64()
|
||||
return f, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// date_part_text_timetz represents the PostgreSQL date_part function for time with time zone type.
|
||||
var date_part_text_timetz = framework.Function2{
|
||||
Name: "date_part",
|
||||
Return: pgtypes.Float64,
|
||||
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.TimeTZ},
|
||||
Strict: true,
|
||||
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
|
||||
field := val1.(string)
|
||||
timetzVal := val2.(timetz.TimeTZ).ToTime()
|
||||
_, currentOffset := timetzVal.Zone()
|
||||
switch strings.ToLower(field) {
|
||||
case "century", "centuries", "day", "days", "decade", "decades", "dow", "doy",
|
||||
"isodow", "isoyear", "julian", "millennium", "millenniums", "month", "months",
|
||||
"quarter", "week", "year", "years":
|
||||
return nil, ErrUnitNotSupported.New(field, "time with time zone")
|
||||
case "timezone":
|
||||
return float64(-currentOffset), nil
|
||||
case "timezone_hour":
|
||||
return float64(-currentOffset / 3600), nil
|
||||
case "timezone_minute":
|
||||
return float64((-currentOffset % 3600) / 60), nil
|
||||
default:
|
||||
result, err := getFieldFromTimeVal(field, timetzVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := result.Float64()
|
||||
return f, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// date_part_text_timestamp represents the PostgreSQL date_part function for timestamp type.
|
||||
var date_part_text_timestamp = framework.Function2{
|
||||
Name: "date_part",
|
||||
Return: pgtypes.Float64,
|
||||
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.Timestamp},
|
||||
Strict: true,
|
||||
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
|
||||
field := val1.(string)
|
||||
tsVal := val2.(time.Time)
|
||||
switch strings.ToLower(field) {
|
||||
case "timezone", "timezone_hour", "timezone_minute":
|
||||
return nil, ErrUnitNotSupported.New(field, "timestamp without time zone")
|
||||
default:
|
||||
result, err := getFieldFromTimeVal(field, tsVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := result.Float64()
|
||||
return f, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// date_part_text_timestamptz represents the PostgreSQL date_part function for timestamp with time zone type.
|
||||
var date_part_text_timestamptz = framework.Function2{
|
||||
Name: "date_part",
|
||||
Return: pgtypes.Float64,
|
||||
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.TimestampTZ},
|
||||
Strict: true,
|
||||
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
|
||||
field := val1.(string)
|
||||
loc, err := GetServerLocation(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tstzVal := val2.(time.Time).In(loc)
|
||||
switch strings.ToLower(field) {
|
||||
case "timezone":
|
||||
// TODO: postgres seem to use server timezone regardless of input value
|
||||
return float64(-28800), nil
|
||||
case "timezone_hour":
|
||||
// TODO: postgres seem to use server timezone regardless of input value
|
||||
return float64(-8), nil
|
||||
case "timezone_minute":
|
||||
// TODO: postgres seem to use server timezone regardless of input value
|
||||
return float64(0), nil
|
||||
default:
|
||||
result, err := getFieldFromTimeVal(field, tstzVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := result.Float64()
|
||||
return f, nil
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// date_part_text_interval represents the PostgreSQL date_part function for interval type.
|
||||
var date_part_text_interval = framework.Function2{
|
||||
Name: "date_part",
|
||||
Return: pgtypes.Float64,
|
||||
Parameters: [2]*pgtypes.DoltgresType{pgtypes.Text, pgtypes.Interval},
|
||||
Strict: true,
|
||||
Callable: func(ctx *sql.Context, _ [3]*pgtypes.DoltgresType, val1, val2 any) (any, error) {
|
||||
field := val1.(string)
|
||||
dur := val2.(duration.Duration)
|
||||
|
||||
// This mirrors the exact logic from extract_text_interval
|
||||
switch strings.ToLower(field) {
|
||||
case "century", "centuries":
|
||||
dec, err := numericFloor(float64(dur.Months) / 12 / 100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := dec.Float64()
|
||||
return f, nil
|
||||
case "day", "days":
|
||||
return float64(dur.Days), nil
|
||||
case "decade", "decades":
|
||||
dec, err := numericFloor(float64(dur.Months) / 12 / 10)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := dec.Float64()
|
||||
return f, nil
|
||||
case "epoch":
|
||||
epoch := float64(duration.SecsPerDay*duration.DaysPerMonth*dur.Months) + float64(duration.SecsPerDay*dur.Days) +
|
||||
(float64(dur.Nanos()) / float64(NanosPerSec))
|
||||
return epoch, nil
|
||||
case "hour", "hours":
|
||||
hours := float64(dur.Nanos()) / float64(NanosPerSec*duration.SecsPerHour)
|
||||
dec, err := numericFloor(hours)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := dec.Float64()
|
||||
return f, nil
|
||||
case "microsecond", "microseconds":
|
||||
secondsInNanos := dur.Nanos() % (NanosPerSec * duration.SecsPerMinute)
|
||||
microseconds := float64(secondsInNanos) / float64(NanosPerMicro)
|
||||
return microseconds, nil
|
||||
case "millennium", "millenniums":
|
||||
dec, err := numericFloor(float64(dur.Months) / 12 / 1000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := dec.Float64()
|
||||
return f, nil
|
||||
case "millisecond", "milliseconds":
|
||||
secondsInNanos := dur.Nanos() % (NanosPerSec * duration.SecsPerMinute)
|
||||
milliseconds := float64(secondsInNanos) / float64(NanosPerMilli)
|
||||
return milliseconds, nil
|
||||
case "minute", "minutes":
|
||||
minutesInNanos := dur.Nanos() % (NanosPerSec * duration.SecsPerHour)
|
||||
minutes := float64(minutesInNanos) / float64(NanosPerSec*duration.SecsPerMinute)
|
||||
dec, err := numericFloor(minutes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := dec.Float64()
|
||||
return f, nil
|
||||
case "month", "months":
|
||||
return float64(dur.Months % 12), nil
|
||||
case "quarter":
|
||||
return float64((dur.Months%12-1)/3 + 1), nil
|
||||
case "second", "seconds":
|
||||
secondsInNanos := dur.Nanos() % (NanosPerSec * duration.SecsPerMinute)
|
||||
seconds := float64(secondsInNanos) / float64(NanosPerSec)
|
||||
return seconds, nil
|
||||
case "year", "years":
|
||||
dec, err := numericFloor(float64(dur.Months) / 12)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, _ := dec.Float64()
|
||||
return f, nil
|
||||
case "dow", "doy", "isodow", "isoyear", "julian", "timezone", "timezone_hour", "timezone_minute", "week":
|
||||
return nil, ErrUnitNotSupported.New(field, "interval")
|
||||
default:
|
||||
return nil, ErrUnitNotSupported.New(field, "interval")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func numericFloor(val any) (*apd.Decimal, error) {
|
||||
switch val.(type) {
|
||||
case int64, float64:
|
||||
// expects these types to Scan from
|
||||
default:
|
||||
return nil, cerrors.Errorf("invalid type for numeric convert: %T", val)
|
||||
}
|
||||
dec := new(apd.Decimal)
|
||||
err := dec.Scan(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = sql.DecimalCtx.Floor(dec, dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dec, nil
|
||||
}
|
||||
Reference in New Issue
Block a user