Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Changelog (English)
- Upgrade Microsoft SQL Server driver to v1.10.0
- Upgrade PostgreSQL driver to v1.12.3
- Oracle datetime values are now passed as strings and converted using `TO_DATE`/`TO_TIMESTAMP` to avoid timezone-related comparison issues in sijms/go-ora. (#55)
- Oracle DATE/TIMESTAMP values are now displayed without timezone suffixes. (#57)

v0.27.6
-------
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Changelog (Japanese)
- Upgrade Microsoft SQL Server driver to v1.10.0
- Upgrade PostgreSQL driver to v1.12.3
- sijms/go-ora でのタイムゾーンに関する比較問題を回避するため、Oracle の日時値を文字列として引き渡し、`TO_DATE`/`TO_TIMESTAMP` を使ってコンバートするようにした (#55)
- Oracle の DATE/TIMESTAMP 値はタイムゾーンなしで表示するようにした (#57)

v0.27.6
-------
Expand Down
4 changes: 4 additions & 0 deletions dialect/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ type Entry struct {

// IdentifierEncloser encloses an identifier with dialect-specific quotes.
IdentifierEncloser func(name string) string

// FormatValue converts a database value into a dialect-specific string representation.
// Returning false means the default conversion should be used instead.
FormatValue func(typeName string, value any) (string, bool)
}

// EncloseIdentifier returns the given name enclosed with
Expand Down
21 changes: 17 additions & 4 deletions dialect/oracle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"strings"
"time"

_ "github.com/sijms/go-ora/v2"

Expand Down Expand Up @@ -33,6 +34,18 @@ var oracleSpec = &dialect.Entry{
TableNameField: "tname",
ColumnNameField: "name",
PlaceHolder: new(placeHolder),
FormatValue: formatValue,
}

func formatValue(typeName string, value any) (string, bool) {
t, ok := value.(time.Time)
if !ok {
return "", false
}
if typeName == "DATE" {
return t.Format("2006-01-02 15:04:05"), true
}
return t.Format("2006-01-02 15:04:05.999999"), true
}

type withFormat struct {
Expand All @@ -45,11 +58,11 @@ func oracleTypeNameToConv(typeName string) func(string) (any, error) {
var layout string

if typeName == "DATE" {
format = "TO_DATE(:v%d,'YYYY/MM/DD HH24:MI:SS')"
layout = "2006/01/02 15:04:05"
format = "TO_DATE(:v%d,'YYYY-MM-DD HH24:MI:SS')"
layout = "2006-01-02 15:04:05"
} else if strings.HasPrefix(typeName, "TIMESTAMP") {
format = "TO_TIMESTAMP(:v%d,'YYYY/MM/DD HH24:MI:SS.FF')"
layout = "2006/01/02 15:04:05.999999"
format = "TO_TIMESTAMP(:v%d,'YYYY-MM-DD HH24:MI:SS.FF')"
layout = "2006-01-02 15:04:05.999999"
} else {
return nil
}
Expand Down
5 changes: 3 additions & 2 deletions edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func newViewer(ss *session) *spread.Viewer {
hl = 1
}
return &spread.Viewer{
Entry: ss.Dialect,
HeaderLines: hl,
Comma: ss.comma(),
Null: ss.Null,
Expand Down Expand Up @@ -80,12 +81,12 @@ func chooseTable(ctx context.Context, tables []string, d *dialect.Entry, ttyout
func doEdit(ctx context.Context, ss *session, command string, pilot commandIn) error {
editor := &spread.Editor{
Viewer: &spread.Viewer{
Entry: ss.Dialect,
HeaderLines: 1,
Comma: ss.comma(),
Null: ss.Null,
},
Entry: ss.Dialect,
Exec: (&askSqlAndExecute{getKey: pilot.GetKey, session: ss}).Exec,
Exec: (&askSqlAndExecute{getKey: pilot.GetKey, session: ss}).Exec,
}
if a, ok := pilot.AutoPilotForCsvi(); ok {
editor.Pilot = misc.AutoCsvi{GetKeyAndSize: a}
Expand Down
52 changes: 22 additions & 30 deletions rowstocsv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,15 @@ type Source interface {
Scan(dest ...any) error
}

func anyToNullString(v any) sql.NullString {
var ns sql.NullString
func anyToNullString(v any) (string, bool) {
if stamp, ok := v.(time.Time); ok {
ns.String = stamp.Format("2006-01-02 15:04:05.999999999 -07:00")
ns.Valid = true
return stamp.Format("2006-01-02 15:04:05.999999999 -07:00"), true
} else if b, ok := v.([]byte); ok {
ns.String = string(b)
ns.Valid = true
return string(b), true
} else if v != nil {
ns.String = fmt.Sprint(v)
ns.Valid = true
return fmt.Sprint(v), true
}
return ns
return "", false
}

func makeBuffers[T any](n int) ([]any, []T) {
Expand All @@ -43,7 +39,7 @@ func makeBuffers[T any](n int) ([]any, []T) {
return refs, data
}

func dump(ctx context.Context, rows Source, conv func(int, *sql.ColumnType, sql.NullString) string, debug bool, write func([]string) error) error {
func dump(ctx context.Context, rows Source, conv func(int, *sql.ColumnType, any) (string, bool), null string, debug bool, write func([]string) error) error {
columns, err := rows.Columns()
if err != nil {
return fmt.Errorf("(sql.Rows) Columns: %w", err)
Expand All @@ -55,7 +51,6 @@ func dump(ctx context.Context, rows Source, conv func(int, *sql.ColumnType, sql.

n := len(columns)
refs, data := makeBuffers[any](n)
//refs, data := makeBuffers[sql.RawBytes](n)
strs := make([]string, len(columns))

columnTypes, err := rows.ColumnTypes()
Expand Down Expand Up @@ -83,17 +78,25 @@ func dump(ctx context.Context, rows Source, conv func(int, *sql.ColumnType, sql.
}

for rows.Next() {
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := ctx.Err(); err != nil {
return err
}
if err := rows.Scan(refs...); err != nil {
return err
}
for i, v := range data {
ns := anyToNullString(v)
strs[i] = conv(i, columnTypes[i], ns)
if conv != nil {
s, ok := conv(i, columnTypes[i], v)
if ok {
strs[i] = s
continue
}
}
if s, ok := anyToNullString(v); ok {
strs[i] = s
} else {
strs[i] = null
}
}
if err := write(strs); err != nil {
return fmt.Errorf("(csv.Writer).Write: %w", err)
Expand All @@ -110,30 +113,19 @@ type Config struct {
UseCRLF bool
Null string
Debug bool
Conv func(int, *sql.ColumnType, sql.NullString) string
Conv func(int, *sql.ColumnType, any) (string, bool)
AutoClose bool
}

func (cfg Config) defaultConv(_ int, _ *sql.ColumnType, v sql.NullString) string {
if v.Valid {
return v.String
}
return cfg.Null
}

func (cfg Config) Dump(ctx context.Context, rows Source, w io.Writer) error {
csvw := csv.NewWriter(w)
defer csvw.Flush()

csvw.Comma = cfg.Comma
csvw.UseCRLF = cfg.UseCRLF

conv := cfg.defaultConv
if cfg.Conv != nil {
conv = cfg.Conv
}
if cfg.AutoClose {
defer rows.Close()
}
return dump(ctx, rows, conv, cfg.Debug, csvw.Write)
return dump(ctx, rows, cfg.Conv, cfg.Null, cfg.Debug, csvw.Write)
}
7 changes: 6 additions & 1 deletion spread/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ func doubleQuoteIfNeed(s string) string {

type Editor struct {
*Viewer
*dialect.Entry
Query func(context.Context, string, ...any) (*sql.Rows, error)
Exec func(context.Context, string, ...any) (sql.Result, error)
}
Expand Down Expand Up @@ -195,6 +194,12 @@ func (editor *Editor) Edit(ctx context.Context, tableAndWhere string, termOut io
Null: editor.Viewer.Null,
Comma: rune(editor.Viewer.Comma),
AutoClose: true,
Conv: func(_ int, ct *sql.ColumnType, v any) (string, bool) {
if f := editor.Entry.FormatValue; f != nil {
return f(ct.DatabaseTypeName(), v)
}
return "", false
},
}.Dump(ctx, rows, w)
rows = nil
return err
Expand Down
9 changes: 9 additions & 0 deletions spread/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package spread

import (
"context"
"database/sql"
"errors"
"io"
"strings"

"github.com/hymkor/csvi"
"github.com/hymkor/csvi/uncsv"
"github.com/hymkor/sqlbless/dialect"
"github.com/hymkor/sqlbless/rowstocsv"
)

Expand All @@ -17,6 +19,7 @@ type KeyBinding struct {
}

type Viewer struct {
*dialect.Entry
HeaderLines int
Comma byte
Null string
Expand All @@ -41,6 +44,12 @@ func (viewer *Viewer) View(ctx context.Context, title string, rows rowstocsv.Sou
Null: viewer.Null,
Comma: rune(viewer.Comma),
AutoClose: true,
Conv: func(_ int, ct *sql.ColumnType, v any) (string, bool) {
if f := viewer.Entry.FormatValue; f != nil {
return f(ct.DatabaseTypeName(), v)
}
return "", false
},
}.Dump(ctx, rows, w)
}

Expand Down
4 changes: 2 additions & 2 deletions test/test-oracle.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ ForEach-Object {
Write-Host $field.Length
return
}
if ( $field[1] -notlike "2015-06-07 20:21:22*" ){
if ( $field[1] -ne "2015-06-07 20:21:22" ){
Write-Host $field[1]
return
}
if ( $field[2] -notlike "2024-08-09 10:11:12.7878*" ){
if ( $field[2] -ne "2024-08-09 10:11:12.7878" ){
Write-Host $field[2]
return
}
Expand Down
Loading