diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..3c0e034 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,122 @@ +# golangci-lint configuration file +# see: https://github.com/golangci/golangci/wiki/Configuration + +run: + timeout: 5m + skip-dirs: + - bin + +linters-settings: + + govet: + check-shadowing: true + + golint: + min-confidence: 0 + + goimports: + local-prefixes: github.com/goccy/go-zetasqlite + + gocyclo: + min-complexity: 10 + + maligned: + suggest-new: true + + depguard: + list-type: blacklist + include-go-root: false + + + misspell: + locale: US + + lll: + line-length: 120 + + nakedret: + max-func-lines: 0 + + gocritic: + disabled-checks: + - whyNoLint + - wrapperFunc + - ifElseChain + - unnamedResult + - paramTypeCombine + - hugeParam + - singleCaseSwitch + - octalLiteral + + enabled-tags: + - performance + - style + - experimental + gci: + sections: + - "standard" + - "default" + - "prefix(github.com/goccy/go-zetasqlite)" + - "blank" + - "dot" + +linters: + enable: + - bodyclose + - rowserrcheck + - gosec + - unconvert + - asciicheck + - gofmt + - goimports + - misspell + - unparam + - dogsled + - nakedret + - gocritic + - godox + - whitespace + - goprintffuncname + - gomodguard + - godot + - nolintlint + - asasalint + - bidichk + - durationcheck + - importas + - tagliatelle + - tenv + - gci + disable: + - stylecheck + - maligned + - prealloc + - gochecknoglobals + - wsl + - testpackage + - gocognit + - depguard + +# Configuration of issue rules +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: + # Exclude lll issues for long lines with go:generate + - linters: + - lll + source: "^//go:generate " + + # Exclude shadow checking on the variable named err and ctx + - text: "shadow: declaration of \"(err|ctx)\"" + linters: + - govet + + # Exclude godox check for TODOs, FIXMEs, and BUGs + - text: "Line contains TODO/BUG/FIXME:" + linters: + - godox + + # Exclude some linters from running on tests files + - path: suite_test\.go + linters: + - typecheck diff --git a/Makefile b/Makefile index c012902..b45e0d0 100644 --- a/Makefile +++ b/Makefile @@ -28,5 +28,8 @@ cover-html: cover lint: lint/install $(GOBIN)/golangci-lint run --timeout 30m +lint/fix: lint/install + $(GOBIN)/golangci-lint run --fix --timeout 30m + lint/install: | $(GOBIN) - GOBIN=$(GOBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2 + GOBIN=$(GOBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.2 diff --git a/driver.go b/driver.go index 3e93a8e..4caa569 100644 --- a/driver.go +++ b/driver.go @@ -7,8 +7,9 @@ import ( "fmt" "sync" - internal "github.com/goccy/go-zetasqlite/internal" "github.com/mattn/go-sqlite3" + + internal "github.com/goccy/go-zetasqlite/internal" ) var ( diff --git a/driver_test.go b/driver_test.go index 3ac1c13..ec3597d 100644 --- a/driver_test.go +++ b/driver_test.go @@ -5,8 +5,9 @@ import ( "database/sql" "testing" - zetasqlite "github.com/goccy/go-zetasqlite" "github.com/google/go-cmp/cmp" + + zetasqlite "github.com/goccy/go-zetasqlite" ) func TestDriver(t *testing.T) { @@ -110,6 +111,9 @@ CREATE TABLE IF NOT EXISTS Singers ( if err != nil { t.Fatal(err) } + if err := rows.Err(); err != nil { + t.Fatal(err) + } resultCatalog, err := zetasqlite.ChangedCatalogFromResult(result) if err != nil { t.Fatal(err) @@ -150,6 +154,9 @@ CREATE TABLE IF NOT EXISTS Singers ( if err != nil { t.Fatal(err) } + if err := rows.Err(); err != nil { + t.Fatal(err) + } resultCatalog, err := zetasqlite.ChangedCatalogFromResult(result) if err != nil { t.Fatal(err) @@ -202,6 +209,9 @@ CREATE TABLE IF NOT EXISTS Singers ( if err != nil { t.Fatal(err) } + if err := rows.Err(); err != nil { + t.Fatal(err) + } if rows.Next() { t.Fatal("found unexpected row; expected no rows") } @@ -236,6 +246,9 @@ CREATE TABLE IF NOT EXISTS Singers ( if err != nil { t.Fatal(err) } + if err := rows.Err(); err != nil { + t.Fatal(err) + } if !rows.Next() { t.Fatal("expected no rows; expected one row") } diff --git a/exec_test.go b/exec_test.go index a1b9948..cab263f 100644 --- a/exec_test.go +++ b/exec_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" - zetasqlite "github.com/goccy/go-zetasqlite" "github.com/google/go-cmp/cmp" + + zetasqlite "github.com/goccy/go-zetasqlite" ) func TestExec(t *testing.T) { @@ -532,7 +533,7 @@ WITH Input AS ( defer rows.Close() type queryRow struct { - JsonRow string + JSONRow string Sum float64 } results := []*queryRow{} @@ -544,15 +545,15 @@ WITH Input AS ( if err := rows.Scan(&jsonRow, &sum); err != nil { t.Fatal(err) } - results = append(results, &queryRow{JsonRow: jsonRow, Sum: sum}) + results = append(results, &queryRow{JSONRow: jsonRow, Sum: sum}) } if rows.Err() != nil { t.Fatal(rows.Err()) } if diff := cmp.Diff(results, []*queryRow{ - {JsonRow: `{"s":{"foo":1,"bar":2,"baz":{"x":"foo","foo":3.14}},"foo":10}`, Sum: 14.14}, - {JsonRow: `{"s":null,"foo":4}`, Sum: 4}, - {JsonRow: `{"s":{"foo":null,"bar":2,"baz":{"x":"fizz","foo":1.59}},"foo":null}`, Sum: 1.59}, + {JSONRow: `{"s":{"foo":1,"bar":2,"baz":{"x":"foo","foo":3.14}},"foo":10}`, Sum: 14.14}, + {JSONRow: `{"s":null,"foo":4}`, Sum: 4}, + {JSONRow: `{"s":{"foo":null,"bar":2,"baz":{"x":"fizz","foo":1.59}},"foo":null}`, Sum: 1.59}, }); diff != "" { t.Errorf("(-want +got):\n%s", diff) } diff --git a/internal/analyzer.go b/internal/analyzer.go index c778cf0..6f35590 100644 --- a/internal/analyzer.go +++ b/internal/analyzer.go @@ -283,7 +283,7 @@ func (a *Analyzer) newStmtAction(ctx context.Context, query string, args []drive return nil, fmt.Errorf("unsupported stmt %s", node.DebugString()) } -func (a *Analyzer) newCreateTableStmtAction(ctx context.Context, query string, args []driver.NamedValue, node *ast.CreateTableStmtNode) (*CreateTableStmtAction, error) { +func (a *Analyzer) newCreateTableStmtAction(_ context.Context, query string, args []driver.NamedValue, node *ast.CreateTableStmtNode) (*CreateTableStmtAction, error) { spec := newTableSpec(a.namePath, node) params := getParamsFromNode(node) queryArgs, err := getArgsFromParams(args, params) @@ -319,7 +319,7 @@ func (a *Analyzer) newCreateTableAsSelectStmtAction(ctx context.Context, _ strin }, nil } -func (a *Analyzer) newCreateFunctionStmtAction(ctx context.Context, query string, args []driver.NamedValue, node *ast.CreateFunctionStmtNode) (*CreateFunctionStmtAction, error) { +func (a *Analyzer) newCreateFunctionStmtAction(ctx context.Context, query string, _ []driver.NamedValue, node *ast.CreateFunctionStmtNode) (*CreateFunctionStmtAction, error) { var spec *FunctionSpec if a.resultTypeIsTemplatedType(node.Signature()) { realStmts, err := a.inferTemplatedTypeByRealType(query, node) @@ -345,7 +345,7 @@ func (a *Analyzer) newCreateFunctionStmtAction(ctx context.Context, query string }, nil } -func (a *Analyzer) newCreateViewStmtAction(ctx context.Context, _ string, args []driver.NamedValue, node *ast.CreateViewStmtNode) (*CreateViewStmtAction, error) { +func (a *Analyzer) newCreateViewStmtAction(ctx context.Context, _ string, _ []driver.NamedValue, node *ast.CreateViewStmtNode) (*CreateViewStmtAction, error) { query, err := newNode(node.Query()).FormatSQL(ctx) if err != nil { return nil, err @@ -529,12 +529,13 @@ func (a *Analyzer) newCommitStmtAction(ctx context.Context, query string, args [ return &CommitStmtAction{}, nil } -func (a *Analyzer) newTruncateStmtAction(ctx context.Context, query string, args []driver.NamedValue, node *ast.TruncateStmtNode) (*TruncateStmtAction, error) { +//nolint:unparam +func (a *Analyzer) newTruncateStmtAction(_ context.Context, _ string, _ []driver.NamedValue, node *ast.TruncateStmtNode) (*TruncateStmtAction, error) { table := node.TableScan().Table().Name() return &TruncateStmtAction{query: fmt.Sprintf("DELETE FROM `%s`", table)}, nil } -func (a *Analyzer) newMergeStmtAction(ctx context.Context, query string, args []driver.NamedValue, node *ast.MergeStmtNode) (*MergeStmtAction, error) { +func (a *Analyzer) newMergeStmtAction(ctx context.Context, _ string, args []driver.NamedValue, node *ast.MergeStmtNode) (*MergeStmtAction, error) { targetTable, err := newNode(node.TableScan()).FormatSQL(ctx) if err != nil { return nil, err @@ -577,8 +578,8 @@ func (a *Analyzer) newMergeStmtAction(ctx context.Context, query string, args [] sourceColumn = colB.Column() targetColumn = colA.Column() } - mergedTableSourceColumnName := fmt.Sprintf("`%s`", string(uniqueColumnName(ctx, sourceColumn))) - mergedTableTargetColumnName := fmt.Sprintf("`%s`", string(uniqueColumnName(ctx, targetColumn))) + mergedTableSourceColumnName := fmt.Sprintf("`%s`", uniqueColumnName(ctx, sourceColumn)) + mergedTableTargetColumnName := fmt.Sprintf("`%s`", uniqueColumnName(ctx, targetColumn)) mergedTableOutputColumns := []string{ mergedTableTargetColumnName, mergedTableSourceColumnName, diff --git a/internal/catalog.go b/internal/catalog.go index 135d9c6..c626dd1 100644 --- a/internal/catalog.go +++ b/internal/catalog.go @@ -187,6 +187,9 @@ func (c *Catalog) Sync(ctx context.Context, conn *Conn) error { if err != nil { return fmt.Errorf("failed to query load catalog: %w", err) } + if err := rows.Err(); err != nil { + return err + } defer rows.Close() for rows.Next() { var ( diff --git a/internal/conn.go b/internal/conn.go index a789f0a..2f7c2bb 100644 --- a/internal/conn.go +++ b/internal/conn.go @@ -80,7 +80,7 @@ func (c *Conn) addTable(spec *TableSpec) { c.cc.Table.Added = append(c.cc.Table.Added, spec) } -//nolint: unused +//nolint:unused func (c *Conn) updateTable(spec *TableSpec) { c.cc.Table.Updated = append(c.cc.Table.Updated, spec) } diff --git a/internal/encoder.go b/internal/encoder.go index c3bcb2e..45723ab 100644 --- a/internal/encoder.go +++ b/internal/encoder.go @@ -129,7 +129,7 @@ func LiteralFromValue(v Value) (string, error) { if err != nil { return "", fmt.Errorf("failed to encode value: %w", err) } - return fmt.Sprintf(`"%s"`, base64.StdEncoding.EncodeToString(b)), nil + return fmt.Sprintf("%q", base64.StdEncoding.EncodeToString(b)), nil } func LiteralFromZetaSQLValue(v types.Value) (string, error) { @@ -156,13 +156,13 @@ func ValueFromZetaSQLValue(v types.Value) (Value, error) { case types.ENUM: return stringValueFromLiteral(v.SQLLiteral(0)) case types.BYTES: - return bytesValueFromLiteral(v.SQLLiteral(0)) + return bytesValueFromLiteral(v.SQLLiteral(0)), nil case types.DATE: - return dateValueFromLiteral(v.ToInt64()) + return dateValueFromLiteral(v.ToInt64()), nil case types.DATETIME: - return datetimeValueFromLiteral(v.ToPacked64DatetimeMicros()) + return datetimeValueFromLiteral(v.ToPacked64DatetimeMicros()), nil case types.TIME: - return timeValueFromLiteral(v.ToPacked64TimeMicros()) + return timeValueFromLiteral(v.ToPacked64TimeMicros()), nil case types.TIMESTAMP: microsec := v.ToUnixMicros() microSecondsInSecond := int64(time.Second) / int64(time.Microsecond) @@ -215,18 +215,18 @@ func stringValueFromLiteral(lit string) (StringValue, error) { return StringValue(v), nil } -func bytesValueFromLiteral(lit string) (BytesValue, error) { +func bytesValueFromLiteral(lit string) BytesValue { // use a workaround because ToBytes doesn't work with certain values. unquoted, err := strconv.Unquote(lit[1:]) if err != nil { - return BytesValue(lit), nil + return BytesValue(lit) } - return BytesValue(unquoted), nil + return BytesValue(unquoted) } -func dateValueFromLiteral(days int64) (DateValue, error) { +func dateValueFromLiteral(days int64) DateValue { t := time.Unix(int64(time.Duration(days)*24*time.Hour/time.Second), 0) - return DateValue(t), nil + return DateValue(t) } const ( @@ -245,7 +245,7 @@ const ( yearMask = 0x3FFF << yearShift ) -func datetimeValueFromLiteral(bit int64) (DatetimeValue, error) { +func datetimeValueFromLiteral(bit int64) DatetimeValue { b := bit >> 20 year := (b & yearMask) >> yearShift month := (b & monthMask) >> monthShift @@ -263,17 +263,17 @@ func datetimeValueFromLiteral(bit int64) (DatetimeValue, error) { int(sec), int(microSec)*1000, time.UTC, ) - return DatetimeValue(t), nil + return DatetimeValue(t) } -func timeValueFromLiteral(bit int64) (TimeValue, error) { +func timeValueFromLiteral(bit int64) TimeValue { b := bit >> 20 hour := (b & hourMask) >> hourShift min := (b & minMask) >> minShift sec := (b & secMask) >> secShift microSec := (bit & microSecMask) >> 0 t := time.Date(0, 0, 0, int(hour), int(min), int(sec), int(microSec)*1000, time.UTC) - return TimeValue(t), nil + return TimeValue(t) } func timestampValueFromLiteral(t time.Time) (TimestampValue, error) { @@ -295,7 +295,7 @@ func numericValueFromLiteral(lit string) (*NumericValue, error) { numericLit := matches[0][1] r := new(big.Rat) r.SetString(numericLit) - if strings.Contains("BIGNUMERIC", lit) { + if strings.Contains(lit, "BIGNUMERIC") { return &NumericValue{Rat: r, isBigNumeric: true}, nil } return &NumericValue{Rat: r}, nil diff --git a/internal/formatter.go b/internal/formatter.go index fbf9c09..5865b15 100644 --- a/internal/formatter.go +++ b/internal/formatter.go @@ -104,7 +104,7 @@ const ( func getInputPattern(input string) InputPattern { trimmed := strings.TrimSpace(input) - if len(trimmed) == 0 { + if trimmed == "" { return InputKeep } if strings.HasPrefix(trimmed, "FROM") { @@ -375,8 +375,7 @@ func (n *AnalyticFunctionCallNode) FormatSQL(ctx context.Context) (string, error if err != nil { return "", err } - args = append(args, startSQL) - args = append(args, endSQL) + args = append(args, startSQL, endSQL) } args = append(args, getWindowRowIDOptionFuncSQL()) input := analyticInputScanFromContext(ctx) @@ -733,7 +732,7 @@ func (n *FilterScanNode) FormatSQL(ctx context.Context) (string, error) { return fmt.Sprintf("%s HAVING %s", input, filter), nil } } - currentQuery := string(removeExpressions.ReplaceAllString(input, "")) + currentQuery := removeExpressions.ReplaceAllString(input, "") // Qualify the statement if the input is not wrapped in parens queryWrappedInParens := currentQuery == "" @@ -907,7 +906,7 @@ func (n *SetOperationScanNode) FormatSQL(ctx context.Context) (string, error) { case ast.SetOperationTypeExceptDistinct: opType = "EXCEPT" default: - opType = "UNKONWN" + opType = "UNKNOWN" } var queries []string for _, item := range n.node.InputItemList() { diff --git a/internal/func_time_parser_test.go b/internal/func_time_parser_test.go index 10b6095..e31321c 100644 --- a/internal/func_time_parser_test.go +++ b/internal/func_time_parser_test.go @@ -68,7 +68,6 @@ func TestTimeParser(t *testing.T) { expectedProgress: 1, }, } { - t.Run(test.name, func(t *testing.T) { progress, result, err := parseDigitRespectingOptionalPlaces(test.text, test.minValue, test.maxValue) if err != nil { @@ -87,7 +86,6 @@ func TestTimeParser(t *testing.T) { if result != int64(test.expectedResult) { t.Fatalf("unexpected result: expected [%d] but got [%d]", test.expectedResult, result) } - }) } } diff --git a/internal/function.go b/internal/function.go index 34306ef..6b83c51 100644 --- a/internal/function.go +++ b/internal/function.go @@ -507,7 +507,7 @@ func EXTRACT(v Value, part, zone string) (Value, error) { func GENERATE_UUID() (Value, error) { id := uuid.NewString() - return StringValue(string(id)), nil + return StringValue(id), nil } func CAST(expr Value, fromType, toType *Type, isSafeCast bool) (Value, error) { diff --git a/internal/function_aggregate.go b/internal/function_aggregate.go index e1d26ad..403f090 100644 --- a/internal/function_aggregate.go +++ b/internal/function_aggregate.go @@ -950,7 +950,7 @@ func (f *HLL_COUNT_INIT) Step(input Value, precision int64, opt *AggregatorOptio } v = murmur3.Sum64(b) } - f.hll.AddRaw(uint64(v)) + f.hll.AddRaw(v) return nil } diff --git a/internal/function_aggregate_option.go b/internal/function_aggregate_option.go index 0b2e691..379963b 100644 --- a/internal/function_aggregate_option.go +++ b/internal/function_aggregate_option.go @@ -112,7 +112,7 @@ type AggregatorOption struct { OrderBy []*AggregateOrderBy } -func parseAggregateOptions(args ...Value) ([]Value, *AggregatorOption, error) { +func parseAggregateOptions(args ...Value) ([]Value, *AggregatorOption) { var ( filteredArgs []Value opt = &AggregatorOption{} @@ -147,5 +147,5 @@ func parseAggregateOptions(args ...Value) ([]Value, *AggregatorOption, error) { continue } } - return filteredArgs, opt, nil + return filteredArgs, opt } diff --git a/internal/function_array.go b/internal/function_array.go index 86d766b..4ec3574 100644 --- a/internal/function_array.go +++ b/internal/function_array.go @@ -3,7 +3,6 @@ package internal import ( "fmt" "strings" - "time" ) func ARRAY_CONCAT(args ...Value) (Value, error) { @@ -97,7 +96,7 @@ func GENERATE_TIMESTAMP_ARRAY(start, end Value, step int64, part string) (Value, cur := start for { arr.values = append(arr.values, cur) - after, err := cur.(TimestampValue).AddValueWithPart(time.Duration(step), part) + after, err := cur.(TimestampValue).AddValueWithPart(step, part) if err != nil { return nil, err } diff --git a/internal/function_bind.go b/internal/function_bind.go index d1ec7f7..2a11901 100644 --- a/internal/function_bind.go +++ b/internal/function_bind.go @@ -61,10 +61,7 @@ func (a *Aggregator) Step(stepArgs ...interface{}) error { if err != nil { return err } - values, opt, err := parseAggregateOptions(values...) - if err != nil { - return err - } + values, opt := parseAggregateOptions(values...) if opt.IgnoreNulls { filtered := []Value{} for _, v := range values { @@ -132,14 +129,8 @@ func (a *WindowAggregator) Step(stepArgs ...interface{}) error { if err != nil { return err } - values, opt, err := parseAggregateOptions(values...) - if err != nil { - return err - } - values, windowOpt, err := parseWindowOptions(values...) - if err != nil { - return err - } + values, opt := parseAggregateOptions(values...) + values, windowOpt := parseWindowOptions(values...) a.once.Do(func() { a.agg.opt = opt }) diff --git a/internal/function_date.go b/internal/function_date.go index 8ec629d..5e04576 100644 --- a/internal/function_date.go +++ b/internal/function_date.go @@ -92,7 +92,7 @@ func DATE_DIFF(a, b time.Time, part string) (Value, error) { _, bWeek := b.ISOWeek() return IntValue(aWeek - bWeek), nil case "MONTH": - return IntValue((a.Year() * 12 + int(a.Month())) - (b.Year() * 12 + int(b.Month()))), nil + return IntValue((a.Year()*12 + int(a.Month())) - (b.Year()*12 + int(b.Month()))), nil case "YEAR": return IntValue(a.Year() - b.Year()), nil } @@ -114,7 +114,7 @@ func DATE_TRUNC(t time.Time, part string) (Value, error) { case "YEAR": return DateValue(time.Time{}.AddDate(t.Year()-1, 0, 0)), nil case "ISOYEAR": - return nil, fmt.Errorf("currently unsupported DATE_TRUNC with ISO_YAER") + return nil, fmt.Errorf("currently unsupported DATE_TRUNC with ISO_YEAR") } return nil, fmt.Errorf("unexpected part value %s", part) } diff --git a/internal/function_format.go b/internal/function_format.go index b081044..5c8e12c 100644 --- a/internal/function_format.go +++ b/internal/function_format.go @@ -15,71 +15,71 @@ type FormatInfo struct { } var formatSpecifierTable = map[rune]*FormatInfo{ - 'd': &FormatInfo{ + 'd': { validate: validateDecimalInteger, parse: parseInteger, }, - 'i': &FormatInfo{ + 'i': { validate: validateDecimalInteger, parse: parseInteger, }, - 'o': &FormatInfo{ + 'o': { validate: validateOctal, parse: parseInteger, }, - 'x': &FormatInfo{ + 'x': { validate: validateHexInteger, parse: parseInteger, }, - 'X': &FormatInfo{ + 'X': { validate: validateHexInteger, parse: parseInteger, }, - 'f': &FormatInfo{ + 'f': { validate: validateDecimalNotation, parse: parseFloat, }, - 'F': &FormatInfo{ + 'F': { validate: validateDecimalNotation, parse: parseFloat, }, - 'e': &FormatInfo{ + 'e': { validate: validateScientificNotation, parse: parseFloat, }, - 'E': &FormatInfo{ + 'E': { validate: validateScientificNotation, parse: parseFloat, }, - 'g': &FormatInfo{ + 'g': { validate: validateDecimalOrScientificNotation, parse: parseFloat, }, - 'G': &FormatInfo{ + 'G': { validate: validateDecimalOrScientificNotation, parse: parseFloat, }, - 'p': &FormatInfo{ + 'p': { validate: validateOneLineJSON, parse: parseOneLineJSON, }, - 'P': &FormatInfo{ + 'P': { validate: validateMultiLineJSON, parse: parseMultiLineJSON, }, - 's': &FormatInfo{ + 's': { validate: validateString, parse: parseString, }, - 't': &FormatInfo{ + 't': { validate: validatePrintableString, parse: parsePrintableString, }, - 'T': &FormatInfo{ + 'T': { validate: validatePrintableString, parse: parsePrintableString, }, - '%': &FormatInfo{ + '%': { validate: validatePercent, parse: parsePercent, }, @@ -321,6 +321,7 @@ func (c *FormatContext) current() rune { return rune(0) } +//nolint:unparam func (c *FormatContext) progress(num int) { c.idx += num } diff --git a/internal/function_hash.go b/internal/function_hash.go index 97f0ba3..73a7a48 100644 --- a/internal/function_hash.go +++ b/internal/function_hash.go @@ -1,3 +1,4 @@ +//nolint:gosec package internal import ( diff --git a/internal/function_interval.go b/internal/function_interval.go index 457e163..8e65c01 100644 --- a/internal/function_interval.go +++ b/internal/function_interval.go @@ -42,10 +42,10 @@ func MAKE_INTERVAL(year, month, day, hour, minute, second int64) (Value, error) func JUSTIFY_DAYS(v *IntervalValue) (Value, error) { if v.Days > 29 { v.Months += v.Days / 30 - v.Days = v.Days % 30 + v.Days %= 30 } else if v.Days < -29 { v.Months += v.Days / 30 - v.Days = v.Days % 30 + v.Days %= 30 } if v.Months > 11 { v.Months -= 12 @@ -60,24 +60,24 @@ func JUSTIFY_DAYS(v *IntervalValue) (Value, error) { func JUSTIFY_HOURS(v *IntervalValue) (Value, error) { if v.Seconds > 59 { v.Minutes += v.Seconds / 60 - v.Seconds = v.Seconds % 60 + v.Seconds %= 60 } else if v.Seconds < -59 { v.Minutes += v.Seconds / 60 - v.Seconds = v.Seconds % 60 + v.Seconds %= 60 } if v.Minutes > 59 { v.Hours += v.Minutes / 60 - v.Minutes = v.Minutes % 60 + v.Minutes %= 60 } else if v.Minutes < -59 { v.Hours += v.Hours / 60 - v.Minutes = v.Minutes % 60 + v.Minutes %= 60 } if v.Hours > 23 { v.Days += v.Hours / 24 - v.Hours = v.Hours % 24 + v.Hours %= 24 } else if v.Hours < -23 { v.Days += v.Hours / 24 - v.Hours = v.Hours % 24 + v.Hours %= 24 } return v, nil } diff --git a/internal/function_json.go b/internal/function_json.go index 574bcdd..ce1ef93 100644 --- a/internal/function_json.go +++ b/internal/function_json.go @@ -9,7 +9,7 @@ import ( ) func JSON_FIELD(v, fieldName string) (Value, error) { - p, err := json.CreatePath(fmt.Sprintf(`$."%s"`, fieldName)) + p, err := json.CreatePath(fmt.Sprintf(`$.%q`, fieldName)) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func JSON_SUBSCRIPT(v string, field Value) (Value, error) { if err != nil { return nil, err } - p, err := json.CreatePath(fmt.Sprintf(`$."%s"`, name)) + p, err := json.CreatePath(fmt.Sprintf(`$.%q`, name)) if err != nil { return nil, err } diff --git a/internal/function_math.go b/internal/function_math.go index 80b7059..3a58a47 100644 --- a/internal/function_math.go +++ b/internal/function_math.go @@ -82,6 +82,7 @@ func IEEE_DIVIDE(x, y Value) (Value, error) { return FloatValue(x64 / y64), nil } +//nolint:gosec func RAND() (Value, error) { rand.Seed(time.Now().UnixNano()) return FloatValue(rand.Float64()), nil diff --git a/internal/function_net.go b/internal/function_net.go index c548df3..9d41253 100644 --- a/internal/function_net.go +++ b/internal/function_net.go @@ -3,13 +3,14 @@ package internal import ( "encoding/binary" "fmt" - "golang.org/x/net/idna" - "golang.org/x/net/publicsuffix" "net" "net/netip" "net/url" "regexp" "strings" + + "golang.org/x/net/idna" + "golang.org/x/net/publicsuffix" ) func NET_HOST(v string) (Value, error) { diff --git a/internal/function_register.go b/internal/function_register.go index df45895..fcbb58c 100644 --- a/internal/function_register.go +++ b/internal/function_register.go @@ -386,10 +386,10 @@ var ( aggregateFuncMap = map[string][]*NameAndFunc{} windowFuncMap = map[string][]*NameAndFunc{} currentTimeFuncMap = map[string]struct{}{ - "current_date": struct{}{}, - "current_datetime": struct{}{}, - "current_time": struct{}{}, - "current_timestamp": struct{}{}, + "current_date": {}, + "current_datetime": {}, + "current_time": {}, + "current_timestamp": {}, } ) @@ -400,22 +400,13 @@ func RegisterFunctions(conn *sqlite3.SQLiteConn) error { var onceErr error registerFuncOnce.Do(func() { for _, info := range normalFuncs { - if err := setupNormalFuncMap(info); err != nil { - onceErr = err - return - } + setupNormalFuncMap(info) } for _, info := range aggregateFuncs { - if err := setupAggregateFuncMap(info); err != nil { - onceErr = err - return - } + setupAggregateFuncMap(info) } for _, info := range windowFuncs { - if err := setupWindowFuncMap(info); err != nil { - onceErr = err - return - } + setupWindowFuncMap(info) } }) if onceErr != nil { @@ -504,7 +495,7 @@ func RegisterFunctions(conn *sqlite3.SQLiteConn) error { return nil } -func setupNormalFuncMap(info *FuncInfo) error { +func setupNormalFuncMap(info *FuncInfo) { normalFuncMap[info.Name] = append(normalFuncMap[info.Name], &NameAndFunc{ Name: fmt.Sprintf("zetasqlite_%s", info.Name), Func: func(args ...interface{}) (interface{}, error) { @@ -535,21 +526,18 @@ func setupNormalFuncMap(info *FuncInfo) error { return EncodeValue(ret) }, }) - return nil } -func setupAggregateFuncMap(info *AggregateFuncInfo) error { +func setupAggregateFuncMap(info *AggregateFuncInfo) { aggregateFuncMap[info.Name] = append(aggregateFuncMap[info.Name], &NameAndFunc{ Name: fmt.Sprintf("zetasqlite_%s", info.Name), Func: info.BindFunc(), }) - return nil } -func setupWindowFuncMap(info *WindowFuncInfo) error { +func setupWindowFuncMap(info *WindowFuncInfo) { windowFuncMap[info.Name] = append(windowFuncMap[info.Name], &NameAndFunc{ Name: fmt.Sprintf("zetasqlite_window_%s", info.Name), Func: info.BindFunc(), }) - return nil } diff --git a/internal/function_string.go b/internal/function_string.go index 0e7cd7d..6e48cc1 100644 --- a/internal/function_string.go +++ b/internal/function_string.go @@ -37,15 +37,15 @@ func CHR(v int64) (Value, error) { } func CODE_POINTS_TO_BYTES(v *ArrayValue) (Value, error) { - bytes := make([]byte, 0, len(v.values)) + b := make([]byte, 0, len(v.values)) for _, vv := range v.values { i64, err := vv.ToInt64() if err != nil { return nil, err } - bytes = append(bytes, byte(i64)) + b = append(b, byte(i64)) } - return BytesValue(bytes), nil + return BytesValue(b), nil } func CODE_POINTS_TO_STRING(v *ArrayValue) (Value, error) { @@ -955,11 +955,11 @@ func SPLIT(value, delimValue Value) (Value, error) { } var delim string = "," if delimValue != nil { - v, err := delimValue.ToString() + delimV, err := delimValue.ToString() if err != nil { return nil, err } - delim = v + delim = delimV } ret := &ArrayValue{} for _, splitted := range strings.Split(v, delim) { diff --git a/internal/function_time_parser.go b/internal/function_time_parser.go index e99ca6f..9f689c4 100644 --- a/internal/function_time_parser.go +++ b/internal/function_time_parser.go @@ -109,70 +109,70 @@ func (i *FormatTimeInfo) Available(typ TimeFormatType) bool { } var formatPatternMap = map[rune]*FormatTimeInfo{ - 'A': &FormatTimeInfo{ + 'A': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: weekOfDayParser, Format: weekOfDayFormatter, }, - 'a': &FormatTimeInfo{ + 'a': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: shortWeekOfDayParser, Format: shortWeekOfDayFormatter, }, - 'B': &FormatTimeInfo{ + 'B': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: monthParser, Format: monthFormatter, }, - 'b': &FormatTimeInfo{ + 'b': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: shortMonthParser, Format: shortMonthFormatter, }, - 'C': &FormatTimeInfo{ + 'C': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: centuryParser, Format: centuryFormatter, }, - 'c': &FormatTimeInfo{ + 'c': { AvailableTypes: []TimeFormatType{ FormatTypeDatetime, FormatTypeTimestamp, }, Parse: ansicParser, Format: ansicFormatter, }, - 'D': &FormatTimeInfo{ + 'D': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: monthDayYearParser, Format: monthDayYearFormatter, }, - 'd': &FormatTimeInfo{ + 'd': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: dayParser, Format: dayFormatter, }, - 'e': &FormatTimeInfo{ + 'e': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: composeParseFunctions("day of month format", []ParseFunction{leadingSpaceAllowedParser, dayParser}), Format: dayFormatter, }, - 'F': &FormatTimeInfo{ + 'F': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, @@ -185,112 +185,112 @@ var formatPatternMap = map[rune]*FormatTimeInfo{ }), Format: yearMonthDayFormatter, }, - 'G': &FormatTimeInfo{ + 'G': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: yearISOParser, Format: yearISOFormatter, }, - 'g': &FormatTimeInfo{ + 'g': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: centuryISOParser, Format: centuryISOFormatter, }, - 'H': &FormatTimeInfo{ + 'H': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: hourParser, Format: hourFormatter, }, - 'h': &FormatTimeInfo{ + 'h': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: shortMonthParser, Format: shortMonthFormatter, }, - 'I': &FormatTimeInfo{ + 'I': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: hour12Parser, Format: hour12Formatter, }, - 'J': &FormatTimeInfo{ + 'J': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: yearISOParser, Format: yearISOFormatter, }, - 'j': &FormatTimeInfo{ + 'j': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: dayOfYearParser, Format: dayOfYearFormatter, }, - 'k': &FormatTimeInfo{ + 'k': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: composeParseFunctions("24-hour clock hour", []ParseFunction{leadingSpaceAllowedParser, hourParser}), Format: hour24SpacePrecedingSingleDigitFormatter, }, - 'l': &FormatTimeInfo{ + 'l': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: composeParseFunctions("12-hour clock hour", []ParseFunction{leadingSpaceAllowedParser, hour12Parser}), Format: hour12SpacePrecedingSingleDigitFormatter, }, - 'M': &FormatTimeInfo{ + 'M': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: minuteParser, Format: minuteFormatter, }, - 'm': &FormatTimeInfo{ + 'm': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: monthNumberParser, Format: monthNumberFormatter, }, - 'n': &FormatTimeInfo{ + 'n': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTime, FormatTypeTimestamp, }, Parse: newLineParser, Format: newLineFormatter, }, - 'P': &FormatTimeInfo{ + 'P': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: smallAMPMParser, Format: smallAMPMFormatter, }, - 'p': &FormatTimeInfo{ + 'p': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: largeAMPMParser, Format: largeAMPMFormatter, }, - 'Q': &FormatTimeInfo{ + 'Q': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: quarterParser, Format: quarterFormatter, }, - 'R': &FormatTimeInfo{ + 'R': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, @@ -301,112 +301,112 @@ var formatPatternMap = map[rune]*FormatTimeInfo{ }), Format: hourMinuteFormatter, }, - 'S': &FormatTimeInfo{ + 'S': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: secondParser, Format: secondFormatter, }, - 's': &FormatTimeInfo{ + 's': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: unixtimeSecondsParser, Format: unixtimeSecondsFormatter, }, - 'T': &FormatTimeInfo{ + 'T': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: hourMinuteSecondParser, Format: hourMinuteSecondFormatter, }, - 't': &FormatTimeInfo{ + 't': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: tabParser, Format: tabFormatter, }, - 'U': &FormatTimeInfo{ + 'U': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: weekOfYearParser, Format: weekOfYearFormatter, }, - 'u': &FormatTimeInfo{ + 'u': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: weekNumberParser, Format: weekNumberFormatter, }, - 'V': &FormatTimeInfo{ + 'V': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: weekOfYearISOParser, Format: weekOfYearISOFormatter, }, - 'W': &FormatTimeInfo{ + 'W': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: weekOfYearParser, Format: weekOfYearFormatter, }, - 'w': &FormatTimeInfo{ + 'w': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: weekNumberZeroBaseParser, Format: weekNumberZeroBaseFormatter, }, - 'X': &FormatTimeInfo{ + 'X': { AvailableTypes: []TimeFormatType{ FormatTypeTime, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: hourMinuteSecondParser, Format: hourMinuteSecondFormatter, }, - 'x': &FormatTimeInfo{ + 'x': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: monthDayYearParser, Format: monthDayYearFormatter, }, - 'Y': &FormatTimeInfo{ + 'Y': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: yearParser, Format: yearFormatter, }, - 'y': &FormatTimeInfo{ + 'y': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTimestamp, }, Parse: yearWithoutCenturyParser, Format: yearWithoutCenturyFormatter, }, - 'Z': &FormatTimeInfo{ + 'Z': { AvailableTypes: []TimeFormatType{ FormatTypeTimestamp, }, Parse: timeZoneParser, Format: timeZoneFormatter, }, - 'z': &FormatTimeInfo{ + 'z': { AvailableTypes: []TimeFormatType{ FormatTypeTimestamp, }, Parse: timeZoneOffsetParser, Format: timeZoneOffsetFormatter, }, - '%': &FormatTimeInfo{ + '%': { AvailableTypes: []TimeFormatType{ FormatTypeDate, FormatTypeDatetime, FormatTypeTime, FormatTypeTimestamp, }, @@ -416,7 +416,7 @@ var formatPatternMap = map[rune]*FormatTimeInfo{ } var postProcessorPatternMap = map[rune]*TimeParserPostProcessor{ - 'p': &TimeParserPostProcessor{ + 'p': { ShouldPostProcessResult: ampmShouldPostProcessResult, PostProcessResult: ampmPostProcessor, }, @@ -497,13 +497,13 @@ func monthParser(text []rune, t *time.Time) (int, error) { dst := strings.ToLower(string(text[:len(month)])) if src == dst { *t = time.Date( - int(t.Year()), + t.Year(), time.Month(monthIdx+1), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return len(month), nil @@ -527,7 +527,7 @@ func shortMonthParser(text []rune, t *time.Time) (int, error) { src := strings.ToLower(string(month))[:shortLen] dst := strings.ToLower(string(text[:shortLen])) if src == dst { - *t = t.AddDate(0, int(monthIdx+1)-int(t.Month()), 0) + *t = t.AddDate(0, monthIdx+1-int(t.Month()), 0) return shortLen, nil } } @@ -555,11 +555,11 @@ func centuryParser(text []rune, t *time.Time) (int, error) { *t = time.Date( int(c*100-99), t.Month(), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return centuryLen, nil @@ -582,11 +582,11 @@ func yearWithoutCenturyParser(text []rune, t *time.Time) (int, error) { *t = time.Date( int(year), t.Month(), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -636,13 +636,13 @@ func dayParser(text []rune, t *time.Time) (int, error) { return 0, fmt.Errorf("could not parse day number: %s", err) } *t = time.Date( - int(t.Year()), + t.Year(), t.Month(), int(days), - int(t.Hour()), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -680,13 +680,13 @@ func hourParser(text []rune, t *time.Time) (int, error) { return 0, fmt.Errorf("could not parse hour number: %s", err) } *t = time.Date( - int(t.Year()), + t.Year(), t.Month(), - int(t.Day()), + t.Day(), int(h), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -729,13 +729,13 @@ func hour12Parser(text []rune, t *time.Time) (int, error) { return 0, fmt.Errorf("could not parse hour number: %s", err) } *t = time.Date( - int(t.Year()), + t.Year(), t.Month(), - int(t.Day()), + t.Day(), int(h), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -759,13 +759,13 @@ func minuteParser(text []rune, t *time.Time) (int, error) { return 0, fmt.Errorf("unexpected minute number: %s", err) } *t = time.Date( - int(t.Year()), + t.Year(), t.Month(), - int(t.Day()), - int(t.Hour()), + t.Day(), + t.Hour(), int(m), - int(t.Second()), - int(t.Nanosecond()), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -835,11 +835,11 @@ func monthNumberParser(text []rune, t *time.Time) (int, error) { *t = time.Date( t.Year(), time.Month(months), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -883,10 +883,10 @@ func largeAMPMParser(text []rune, t *time.Time) (int, error) { } func ampmPostProcessor(text []rune, t *time.Time) { - morning := strings.ToLower(string(text)) == "am" + morning := strings.EqualFold(string(text), "am") hour := t.Hour() if morning { - hour = hour % 12 + hour %= 12 } if !morning && hour < 12 { hour += 12 @@ -894,11 +894,11 @@ func ampmPostProcessor(text []rune, t *time.Time) { *t = time.Date( t.Year(), t.Month(), - int(t.Day()), - int(hour), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Day(), + hour, + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) } @@ -957,13 +957,13 @@ func secondParser(text []rune, t *time.Time) (int, error) { return 0, fmt.Errorf("unexpected second number: %s", err) } *t = time.Date( - int(t.Year()), + t.Year(), t.Month(), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), + t.Day(), + t.Hour(), + t.Minute(), int(s), - int(t.Nanosecond()), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -1057,11 +1057,11 @@ func yearParser(text []rune, t *time.Time) (int, error) { *t = time.Date( int(y), t.Month(), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), - int(t.Second()), - int(t.Nanosecond()), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), t.Location(), ) return progress, nil @@ -1104,7 +1104,7 @@ func parseTimeFormat(formatStr, targetStr string, typ TimeFormatType) (*time.Tim targetIdx int formatIdx int ) - epoch := time.Unix(0, 0) + epoch := time.Unix(0, 0).UTC() var ret = &epoch var tokenToParseIndices = map[rune][2]int{} @@ -1399,12 +1399,12 @@ func timeZoneRFC3339Formatter(t *time.Time) ([]rune, error) { return []rune(t.Format("-07:00")), nil } -var timePrecisionMatcher = regexp.MustCompile(`[0-9]{2}\.?[0-9]*`) +var timePrecisionMatcher = regexp.MustCompile(`\d{2}\.?\d*`) func timePrecisionParser(precision int, text []rune, t *time.Time) (int, error) { const maxNanosecondsLength = 9 extracted := timePrecisionMatcher.FindString(string(text)) - if len(extracted) == 0 { + if extracted == "" { return 0, fmt.Errorf("failed to parse seconds.nanoseconds for %s", string(text)) } fmtLen := len(extracted) @@ -1416,7 +1416,7 @@ func timePrecisionParser(precision int, text []rune, t *time.Time) (int, error) if len(nanoseconds) > precision { nanoseconds = nanoseconds[:precision] } - nanoseconds = nanoseconds + strings.Repeat("0", maxNanosecondsLength-len(nanoseconds)) + nanoseconds += strings.Repeat("0", maxNanosecondsLength-len(nanoseconds)) } s, err := strconv.ParseInt(seconds, 10, 64) if err != nil { @@ -1427,11 +1427,11 @@ func timePrecisionParser(precision int, text []rune, t *time.Time) (int, error) return 0, fmt.Errorf("failed to parse nanoseconds parameter for %s: %w", string(text), err) } *t = time.Date( - int(t.Year()), + t.Year(), t.Month(), - int(t.Day()), - int(t.Hour()), - int(t.Minute()), + t.Day(), + t.Hour(), + t.Minute(), int(s), int(n), t.Location(), diff --git a/internal/function_window.go b/internal/function_window.go index c96d963..f44b77a 100644 --- a/internal/function_window.go +++ b/internal/function_window.go @@ -280,7 +280,6 @@ func (f *WINDOW_MIN) Done(agg *WindowFuncAggregatedStatus) (Value, error) { min = value } } - } return nil }); err != nil { @@ -651,6 +650,7 @@ func (f *WINDOW_PERCENTILE_CONT) Done(agg *WindowFuncAggregatedStatus) (Value, e return FloatValue(0), nil } + //nolint:gocritic // if ceilingRowNumber = floorRowNumber = rowNumber, return value at rownNumber which is equivalent of floorValue if ceilingRowNumber == floorRowNumber && ceilingRowNumber == rowNumber { return floorValue, nil @@ -849,7 +849,7 @@ func (f *WINDOW_DENSE_RANK) Done(agg *WindowFuncAggregatedStatus) (Value, error) return nil } if start != end { - return fmt.Errorf("Rank must be same value of start and end") + return fmt.Errorf("rank must be same value of start and end") } lastIdx := start var ( @@ -1009,7 +1009,7 @@ func (f *WINDOW_NTILE) Done(agg *WindowFuncAggregatedStatus) (Value, error) { return nil } length := int64(len(values)) - dupCount := int64(length/f.num) - 1 + dupCount := length/f.num - 1 if length%f.num > 0 { dupCount++ } diff --git a/internal/function_window_option.go b/internal/function_window_option.go index 72d20c0..2327571 100644 --- a/internal/function_window_option.go +++ b/internal/function_window_option.go @@ -286,10 +286,10 @@ func (s *WindowFuncStatus) Partition() (string, error) { return strings.Join(partitions, "_"), nil } -func parseWindowOptions(args ...Value) ([]Value, *WindowFuncStatus, error) { +func parseWindowOptions(args ...Value) ([]Value, *WindowFuncStatus) { var ( filteredArgs []Value - opt *WindowFuncStatus = &WindowFuncStatus{} + opt = &WindowFuncStatus{} ) for _, arg := range args { if arg == nil { @@ -324,7 +324,7 @@ func parseWindowOptions(args ...Value) ([]Value, *WindowFuncStatus, error) { continue } } - return filteredArgs, opt, nil + return filteredArgs, opt } type WindowOrderedValue struct { @@ -432,7 +432,6 @@ func (s *WindowFuncAggregatedStatus) Done(cb func([]Value, int, int) error) erro } return false }) - } s.SortedValues = sortedValues start, err := s.getIndexFromBoundary(s.Start) diff --git a/internal/name_path.go b/internal/name_path.go index 84d589d..1813ef4 100644 --- a/internal/name_path.go +++ b/internal/name_path.go @@ -16,7 +16,7 @@ func (p *NamePath) isInformationSchema(path []string) bool { } // If INFORMATION_SCHEMA is at the end of path, ignore it. for _, subPath := range path[:len(path)-1] { - if strings.ToLower(subPath) == "information_schema" { + if strings.EqualFold(subPath, "information_schema") { return true } } diff --git a/internal/rows.go b/internal/rows.go index 80944d9..a52e66a 100644 --- a/internal/rows.go +++ b/internal/rows.go @@ -58,12 +58,12 @@ func (r *Rows) Close() (e error) { return r.rows.Close() } -func (r *Rows) columnTypes() ([]*Type, error) { - types := make([]*Type, 0, len(r.columns)) +func (r *Rows) columnTypes() []*Type { + ret := make([]*Type, 0, len(r.columns)) for _, col := range r.columns { - types = append(types, col.Type) + ret = append(ret, col.Type) } - return types, nil + return ret } func (r *Rows) Next(dest []driver.Value) error { @@ -79,10 +79,7 @@ func (r *Rows) Next(dest []driver.Value) error { if err := r.rows.Err(); err != nil { return err } - colTypes, err := r.columnTypes() - if err != nil { - return err - } + colTypes := r.columnTypes() values := make([]interface{}, 0, len(dest)) for i := 0; i < len(dest); i++ { var v interface{} @@ -268,11 +265,11 @@ func (r *Rows) assignInterfaceValue(src Value, dst reflect.Value, typ *Type) err } dst.Set(reflect.ValueOf(datetime)) case types.TIME: - time, err := src.ToJSON() + t, err := src.ToJSON() if err != nil { return err } - dst.Set(reflect.ValueOf(time)) + dst.Set(reflect.ValueOf(t)) case types.TIMESTAMP: t, err := src.ToTime() if err != nil { @@ -289,11 +286,11 @@ func (r *Rows) assignInterfaceValue(src Value, dst reflect.Value, typ *Type) err } dst.Set(reflect.ValueOf(s)) case types.JSON: - json, err := src.ToJSON() + v, err := src.ToJSON() if err != nil { return err } - dst.Set(reflect.ValueOf(json)) + dst.Set(reflect.ValueOf(v)) case types.STRUCT: s, err := src.ToStruct() if err != nil { diff --git a/internal/spec.go b/internal/spec.go index c343f9e..fb1c831 100644 --- a/internal/spec.go +++ b/internal/spec.go @@ -317,8 +317,6 @@ func (s *ColumnSpec) SQLiteSchema() string { typ = "TEXT" case types.INTERVAL: typ = "TEXT" - case types.UNKNOWN: - fallthrough default: typ = "UNKNOWN" } diff --git a/internal/stmt.go b/internal/stmt.go index 54f1f01..1aaba39 100644 --- a/internal/stmt.go +++ b/internal/stmt.go @@ -242,6 +242,14 @@ func (s *QueryStmt) Query(args []driver.Value) (driver.Rows, error) { err, ) } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf( + "failed to query %s: args: %v: %w", + s.formattedQuery, + newArgs, + err, + ) + } return &Rows{rows: rows, columns: s.outputColumns}, nil } diff --git a/internal/stmt_action.go b/internal/stmt_action.go index 828ced9..8dee05e 100644 --- a/internal/stmt_action.go +++ b/internal/stmt_action.go @@ -380,6 +380,9 @@ func (a *QueryStmtAction) ExplainQueryPlan(ctx context.Context, conn *Conn) erro if err != nil { return fmt.Errorf("failed to explain query plan: %w", err) } + if err := rows.Err(); err != nil { + return fmt.Errorf("failed to explain query plan: %w", err) + } defer rows.Close() fmt.Println("|selectid|order|from|detail|") fmt.Println("----------------------------") @@ -407,6 +410,9 @@ func (a *QueryStmtAction) QueryContext(ctx context.Context, conn *Conn) (*Rows, if err != nil { return nil, fmt.Errorf("failed to query %s: %w", a.query, err) } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to query %s: %w", a.query, err) + } return &Rows{conn: conn, rows: rows, columns: a.outputColumns}, nil } diff --git a/internal/util.go b/internal/util.go index 378bb49..9f07f67 100644 --- a/internal/util.go +++ b/internal/util.go @@ -9,8 +9,8 @@ import ( ) var ( - timeZoneOffsetPartialPattern = regexp.MustCompile(`([-+][0-9]{2})`) - timeZoneOffsetPattern = regexp.MustCompile(`([-+][0-9]{2}):([0-9]{2})`) + timeZoneOffsetPartialPattern = regexp.MustCompile(`([-+]\d{2})`) + timeZoneOffsetPattern = regexp.MustCompile(`([-+]\d{2}):(\d{2})`) locationCacheMap = map[string]*time.Location{} locationCacheMu sync.RWMutex ) diff --git a/internal/value.go b/internal/value.go index 457362c..c90334b 100644 --- a/internal/value.go +++ b/internal/value.go @@ -1613,7 +1613,7 @@ func (d DateValue) Format(verb rune) string { case 't': return formatted case 'T': - return fmt.Sprintf(`DATE "%s"`, formatted) + return fmt.Sprintf(`DATE %q`, formatted) } return formatted } @@ -1765,7 +1765,7 @@ func (d DatetimeValue) Format(verb rune) string { case 't': return formatted case 'T': - return fmt.Sprintf(`DATETIME "%s"`, formatted) + return fmt.Sprintf(`DATETIME %q`, formatted) } return formatted } @@ -1882,7 +1882,7 @@ func (t TimeValue) Format(verb rune) string { case 't': return formatted case 'T': - return fmt.Sprintf(`TIME "%s"`, formatted) + return fmt.Sprintf(`TIME %q`, formatted) } return formatted } @@ -1893,20 +1893,20 @@ func (t TimeValue) Interface() interface{} { type TimestampValue time.Time -func (t TimestampValue) AddValueWithPart(v time.Duration, part string) (Value, error) { +func (t TimestampValue) AddValueWithPart(v int64, part string) (Value, error) { switch part { case "MICROSECOND": - return TimestampValue(time.Time(t).Add(v * time.Microsecond)), nil + return TimestampValue(time.Time(t).Add(time.Duration(v) * time.Microsecond)), nil case "MILLISECOND": - return TimestampValue(time.Time(t).Add(v * time.Millisecond)), nil + return TimestampValue(time.Time(t).Add(time.Duration(v) * time.Millisecond)), nil case "SECOND": - return TimestampValue(time.Time(t).Add(v * time.Second)), nil + return TimestampValue(time.Time(t).Add(time.Duration(v) * time.Second)), nil case "MINUTE": - return TimestampValue(time.Time(t).Add(v * time.Minute)), nil + return TimestampValue(time.Time(t).Add(time.Duration(v) * time.Minute)), nil case "HOUR": - return TimestampValue(time.Time(t).Add(v * time.Hour)), nil + return TimestampValue(time.Time(t).Add(time.Duration(v) * time.Hour)), nil case "DAY": - return TimestampValue(time.Time(t).Add(v * time.Hour * 24)), nil + return TimestampValue(time.Time(t).Add(time.Duration(v) * time.Hour * 24)), nil default: return nil, fmt.Errorf("unknown part value for timestamp: %s", part) } @@ -2043,20 +2043,20 @@ func (t TimestampValue) ToRat() (*big.Rat, error) { return nil, fmt.Errorf("failed to convert *big.Rat from timestamp %v", t) } -func (d TimestampValue) Format(verb rune) string { +func (t TimestampValue) Format(verb rune) string { const timestampPrintableFormat = "2006-01-02 15:04:05" - formatted := time.Time(d).UTC().Format(timestampPrintableFormat) + "+00" + formatted := time.Time(t).UTC().Format(timestampPrintableFormat) + "+00" switch verb { case 't': return formatted case 'T': - return fmt.Sprintf(`TIMESTAMP "%s"`, formatted) + return fmt.Sprintf(`TIMESTAMP %q`, formatted) } return formatted } -func (d TimestampValue) Interface() interface{} { - return time.Time(d).Format(time.RFC3339) +func (t TimestampValue) Interface() interface{} { + return time.Time(t).Format(time.RFC3339) } type IntervalValue struct { @@ -2331,9 +2331,9 @@ func (v *SafeValue) Interface() interface{} { } var ( - dateRe = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}$`) - datetimeRe = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}[T\s][0-9]{2}:[0-9]{2}:[0-9]{2}$`) - timeRe = regexp.MustCompile(`^[0-9]{2}:[0-9]{2}:[0-9]{2}`) + dateRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) + datetimeRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}$`) + timeRe = regexp.MustCompile(`^\d{2}:\d{2}:\d{2}`) ) func isDate(date string) bool { @@ -2344,8 +2344,8 @@ func isDatetime(datetime string) bool { return datetimeRe.MatchString(datetime) } -func isTime(time string) bool { - return timeRe.MatchString(time) +func isTime(v string) bool { + return timeRe.MatchString(v) } func isTimestamp(timestamp string) bool { @@ -2428,7 +2428,7 @@ func TimestampFromInt64Value(v int64) (time.Time, error) { } func parseInterval(v string) (*IntervalValue, error) { - if len(v) == 0 { + if v == "" { return nil, fmt.Errorf("interval value is empty") } isNegative := v[0] == '-' diff --git a/internal/wildcard_table.go b/internal/wildcard_table.go index d88a095..a926c27 100644 --- a/internal/wildcard_table.go +++ b/internal/wildcard_table.go @@ -18,7 +18,7 @@ func (c *Catalog) isWildcardTable(path []string) bool { return false } lastPath := path[len(path)-1] - if len(lastPath) == 0 { + if lastPath == "" { return false } lastChar := lastPath[len(lastPath)-1] diff --git a/query_test.go b/query_test.go index 0d9102b..795a715 100644 --- a/query_test.go +++ b/query_test.go @@ -10,12 +10,13 @@ import ( "testing" "time" - zetasqlite "github.com/goccy/go-zetasqlite" "github.com/google/go-cmp/cmp" + + zetasqlite "github.com/goccy/go-zetasqlite" ) func TestQuery(t *testing.T) { - os.Setenv("TZ", "UTC") + t.Setenv("TZ", "UTC") now := time.Now() ctx := context.Background() ctx = zetasqlite.WithCurrentTime(ctx, now) @@ -573,7 +574,7 @@ FROM Items`, expectedErr: "OFFSET(6) is out of range", }, // INVALID_ARGUMENT: Subscript access using [INT64] is not supported on values of type JSON [at 2:34] - //{ + // { // name: "json", // query: ` // SELECT json_value.class.students[0]['name'] AS first_student @@ -589,7 +590,7 @@ FROM Items`, // {nil}, // {"John"}, // }, - //}, + // }, { name: "date operator", query: `SELECT DATE "2020-09-22" + 1 AS day_later, DATE "2020-09-22" - 7 AS week_ago`, @@ -1007,18 +1008,18 @@ SELECT LOGICAL_OR(x) AS logical_or FROM toks`, { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "value": "pear", }, - map[string]interface{}{ + { "count": int64(3), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "value": "apple", }, - map[string]interface{}{ + { "count": int64(2), }, }, @@ -1033,18 +1034,18 @@ SELECT LOGICAL_OR(x) AS logical_or FROM toks`, { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "value": "pear", }, - map[string]interface{}{ + { "count": int64(3), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "value": nil, }, - map[string]interface{}{ + { "count": int64(2), }, }, @@ -1066,18 +1067,18 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "value": "pear", }, - map[string]interface{}{ + { "sum": int64(6), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "value": "banana", }, - map[string]interface{}{ + { "sum": int64(5), }, }, @@ -1092,18 +1093,18 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "value": "pear", }, - map[string]interface{}{ + { "sum": int64(0), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "value": "apple", }, - map[string]interface{}{ + { "sum": nil, }, }, @@ -1118,18 +1119,18 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "value": nil, }, - map[string]interface{}{ + { "sum": int64(2), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "value": "apple", }, - map[string]interface{}{ + { "sum": int64(0), }, }, @@ -1144,18 +1145,18 @@ SELECT APPROX_TOP_SUM(x, weight, 2) FROM UNNEST([ { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "value": "apple", }, - map[string]interface{}{ + { "sum": int64(0), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "value": nil, }, - map[string]interface{}{ + { "sum": nil, }, }, @@ -1755,13 +1756,13 @@ FROM cte LIMIT 1`, // { // name: `percentile_cont with respect nulls`, // query: ` - //SELECT + // SELECT // PERCENTILE_CONT(x, 0 RESPECT NULLS) OVER() AS min, // PERCENTILE_CONT(x, 0.01 RESPECT NULLS) OVER() AS percentile1, // PERCENTILE_CONT(x, 0.5 RESPECT NULLS) OVER() AS median, // PERCENTILE_CONT(x, 0.9 RESPECT NULLS) OVER() AS percentile90, // PERCENTILE_CONT(x, 1 RESPECT NULLS) OVER() AS max - //FROM UNNEST([0, 3, NULL, 1, 2]) AS x LIMIT 1`, + // FROM UNNEST([0, 3, NULL, 1, 2]) AS x LIMIT 1`, // expectedRows: [][]interface{}{ // {nil, float64(0), float64(1), float64(2.6), float64(3)}, // }, @@ -2081,12 +2082,12 @@ WITH Produce AS FROM Produce p JOIN Numbers n ON p.item = n.item AND p.category = n.category `, expectedRows: [][]interface{}{ - []interface{}{"banana", "fruit", int64(2), int64(1), int64(1)}, - []interface{}{"apple", "fruit", int64(8), int64(2), int64(2)}, - []interface{}{"leek", "vegetable", int64(2), int64(3), int64(1)}, - []interface{}{"cabbage", "vegetable", int64(9), int64(2), int64(2)}, - []interface{}{"lettuce", "vegetable", int64(10), int64(4), int64(3)}, - []interface{}{"kale", "vegetable", int64(23), int64(1), int64(4)}, + {"banana", "fruit", int64(2), int64(1), int64(1)}, + {"apple", "fruit", int64(8), int64(2), int64(2)}, + {"leek", "vegetable", int64(2), int64(3), int64(1)}, + {"cabbage", "vegetable", int64(9), int64(2), int64(2)}, + {"lettuce", "vegetable", int64(10), int64(4), int64(3)}, + {"kale", "vegetable", int64(23), int64(1), int64(4)}, }, }, { @@ -2298,24 +2299,24 @@ INNER JOIN unnest(['lettuce']) in_stock_items ON in_stock_items = item;`, { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "": float64(1), }, - map[string]interface{}{ + { "": float64(2), }, - map[string]interface{}{ + { "": float64(3), }, }, []map[string]interface{}{ - map[string]interface{}{ + { "": float64(4), }, - map[string]interface{}{ + { "": float64(5), }, - map[string]interface{}{ + { "": float64(6), }, }, @@ -2330,7 +2331,7 @@ INNER JOIN unnest(['lettuce']) in_stock_items ON in_stock_items = item;`, { []interface{}{ []map[string]interface{}{ - map[string]interface{}{ + { "": []interface{}{ float64(1), float64(2), @@ -2339,7 +2340,7 @@ INNER JOIN unnest(['lettuce']) in_stock_items ON in_stock_items = item;`, }, }, []map[string]interface{}{ - map[string]interface{}{ + { "": []interface{}{ float64(4), float64(5), @@ -3074,104 +3075,104 @@ SELECT characters, CHARACTER_LENGTH(characters) FROM example`, expectedRows: [][]interface{}{{"AÿȁЀ", "a例", nil, nil}}, }, // TODO: currently collate function is unsupported. - //{ + // { // name: "collate", // query: ` - //WITH Words AS ( + // WITH Words AS ( // SELECT COLLATE('a', 'und:ci') AS char1, COLLATE('Z', 'und:ci') AS char2 - //) SELECT (Words.char1 < Words.char2) FROM Words`, + // ) SELECT (Words.char1 < Words.char2) FROM Words`, // expectedRows: [][]interface{}{{true}}, - //}, + // }, { name: "concat", query: `SELECT CONCAT('T.P.', ' ', 'Bar'), CONCAT('Summer', ' ', 1923), CONCAT("abc"), CONCAT(1), CONCAT('A', NULL, 'C'), CONCAT(NULL)`, expectedRows: [][]interface{}{{"T.P. Bar", "Summer 1923", "abc", "1", nil, nil}}, }, // TODO: currently unsupported CONTAINS_SUBSTR function because ZetaSQL library doesn't support it. - //{ + // { // name: "contains_substr true", // query: `SELECT CONTAINS_SUBSTR('the blue house', 'Blue house')`, // expectedRows: [][]interface{}{{true}}, - //}, - //{ + // }, + // { // name: "contains_substr false", // query: `SELECT CONTAINS_SUBSTR('the red house', 'blue')`, // expectedRows: [][]interface{}{{false}}, - //}, - //{ + // }, + // { // name: "contains_substr normalize", // query: `SELECT '\u2168 day' AS a, 'IX' AS b, CONTAINS_SUBSTR('\u2168', 'IX')`, // expectedRows: [][]interface{}{{"Ⅸ day", "IX", true}}, - //}, - //{ + // }, + // { // name: "contains_substr struct_field", // query: `SELECT CONTAINS_SUBSTR((23, 35, 41), '35')`, // expectedRows: [][]interface{}{{true}}, - //}, - //{ + // }, + // { // name: "contains_substr recursive", // query: `SELECT CONTAINS_SUBSTR(('abc', ['def', 'ghi', 'jkl'], 'mno'), 'jk')`, // expectedRows: [][]interface{}{{true}}, - //}, - //{ + // }, + // { // name: "contains_substr struct with null", // query: `SELECT CONTAINS_SUBSTR((23, NULL, 41), '41')`, // expectedRows: [][]interface{}{{true}}, - //}, - //{ + // }, + // { // name: "contains_substr struct with null2", // query: `SELECT CONTAINS_SUBSTR((23, NULL, 41), '35')`, // expectedRows: [][]interface{}{{nil}}, - //}, - //{ + // }, + // { // name: "contains_substr nil", // query: `SELECT CONTAINS_SUBSTR('hello', NULL)`, // expectedErr: true, - //}, - //{ + // }, + // { // name: "contains_substr for table all rows", // query: ` - //WITH Recipes AS ( + // WITH Recipes AS ( // SELECT 'Blueberry pancakes' as Breakfast, 'Egg salad sandwich' as Lunch, 'Potato dumplings' as Dinner UNION ALL // SELECT 'Potato pancakes', 'Toasted cheese sandwich', 'Beef stroganoff' UNION ALL // SELECT 'Ham scramble', 'Steak avocado salad', 'Tomato pasta' UNION ALL // SELECT 'Avocado toast', 'Tomato soup', 'Blueberry salmon' UNION ALL // SELECT 'Corned beef hash', 'Lentil potato soup', 'Glazed ham' - //) SELECT * FROM Recipes WHERE CONTAINS_SUBSTR(Recipes, 'toast')`, + // ) SELECT * FROM Recipes WHERE CONTAINS_SUBSTR(Recipes, 'toast')`, // expectedRows: [][]interface{}{ // {"Potato pancakes", "Toasted cheese sandwich", "Beef stroganoff"}, // {"Avocado toast", "Tomato soup", "Blueberry samon"}, // }, // }, - //{ + // { // name: "contains_substr for table specified rows", // query: ` - //WITH Recipes AS ( + // WITH Recipes AS ( // SELECT 'Blueberry pancakes' as Breakfast, 'Egg salad sandwich' as Lunch, 'Potato dumplings' as Dinner UNION ALL // SELECT 'Potato pancakes', 'Toasted cheese sandwich', 'Beef stroganoff' UNION ALL // SELECT 'Ham scramble', 'Steak avocado salad', 'Tomato pasta' UNION ALL // SELECT 'Avocado toast', 'Tomato soup', 'Blueberry salmon' UNION ALL // SELECT 'Corned beef hash', 'Lentil potato soup', 'Glazed ham' - //) SELECT * FROM Recipes WHERE CONTAINS_SUBSTR((Lunch, Dinner), 'potato')`, + // ) SELECT * FROM Recipes WHERE CONTAINS_SUBSTR((Lunch, Dinner), 'potato')`, // expectedRows: [][]interface{}{ // {"Bluberry pancakes", "Egg salad sandwich", "Potato dumplings"}, // {"Corned beef hash", "Lentil potato soup", "Glazed ham"}, // }, - //}, - //{ + // }, + // { // name: "contains_substr for table except", // query: ` - //WITH Recipes AS ( + // WITH Recipes AS ( // SELECT 'Blueberry pancakes' as Breakfast, 'Egg salad sandwich' as Lunch, 'Potato dumplings' as Dinner UNION ALL // SELECT 'Potato pancakes', 'Toasted cheese sandwich', 'Beef stroganoff' UNION ALL // SELECT 'Ham scramble', 'Steak avocado salad', 'Tomato pasta' UNION ALL // SELECT 'Avocado toast', 'Tomato soup', 'Blueberry salmon' UNION ALL // SELECT 'Corned beef hash', 'Lentil potato soup', 'Glazed ham' - //) SELECT * FROM Recipes WHERE CONTAINS_SUBSTR((SELECT AS STRUCT Recipes.* EXCEPT (Lunch, Dinner)), 'potato')`, + // ) SELECT * FROM Recipes WHERE CONTAINS_SUBSTR((SELECT AS STRUCT Recipes.* EXCEPT (Lunch, Dinner)), 'potato')`, // expectedRows: [][]interface{}{ // {"Potato pancakes", "Toasted cheese sandwich", "Beef stroganoff"}, // }, - //}, + // }, { name: "ends_with", query: `SELECT ENDS_WITH('apple', 'e'), ENDS_WITH('banana', 'e'), ENDS_WITH('orange', 'e'), ENDS_WITH('foo', NULL), ENDS_WITH(NULL, 'foo')`, diff --git a/timestamp_test.go b/timestamp_test.go index dbcc5b8..038c97c 100644 --- a/timestamp_test.go +++ b/timestamp_test.go @@ -1,10 +1,11 @@ package zetasqlite_test import ( - "github.com/goccy/go-zetasqlite" "os" "testing" "time" + + "github.com/goccy/go-zetasqlite" ) func TestTimestamp(t *testing.T) {