Skip to content

Commit d3e6e1f

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents ad9cc45 + 9972471 commit d3e6e1f

File tree

1 file changed

+59
-22
lines changed

1 file changed

+59
-22
lines changed

view/extension/handler/loader.go

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"compress/gzip"
77
"context"
88
"encoding/json"
9+
"errors"
910
"fmt"
1011
"github.com/viant/afs"
1112
"github.com/viant/datly/utils/types"
@@ -42,56 +43,92 @@ func (l *LoadData) Exec(ctx context.Context, session handler.Session) (interface
4243
if !ok || err != nil {
4344
return nil, fmt.Errorf("invalid Loader URL: %w", err)
4445
}
46+
4547
var URL string
46-
switch URLValue.(type) {
48+
switch v := URLValue.(type) {
4749
case string:
48-
URL = URLValue.(string)
50+
URL = v
4951
case *string:
50-
URL = *URLValue.(*string)
52+
URL = *v
5153
default:
52-
return nil, fmt.Errorf("invalid Loader URL: expected %T, but had %T", URL, URLValue)
54+
return nil, fmt.Errorf("invalid Loader URL: expected %T, but had %T", "", URLValue)
5355
}
5456

57+
// Prefer .gz if the plain URL doesn't exist.
5558
if ok, _ := l.fs.Exists(ctx, URL); !ok {
5659
if ok, _ := l.fs.Exists(ctx, URL+".gz"); ok {
5760
URL += ".gz"
5861
}
5962
}
6063

61-
isCompressed := strings.HasSuffix(URL, ".gz")
64+
// Download compressed or plain bytes (API returns []byte).
6265
data, err := l.fs.DownloadWithURL(ctx, URL)
6366
if err != nil {
6467
return nil, fmt.Errorf("failed to load URL: %w", err)
6568
}
66-
if isCompressed {
67-
reader, err := gzip.NewReader(bytes.NewReader(data))
69+
70+
// Build a streaming reader chain; avoid io.ReadAll on gzip.
71+
var r io.Reader = bytes.NewReader(data)
72+
if strings.HasSuffix(URL, ".gz") {
73+
gzr, err := gzip.NewReader(r)
6874
if err != nil {
6975
return nil, fmt.Errorf("failed to decompress URL: failed to create reader: %w (used URL: %s)", err, URL)
7076
}
71-
defer reader.Close()
72-
if data, err = io.ReadAll(reader); err != nil {
73-
return nil, fmt.Errorf("failed to decompress URL:%w (used URL: %s)", err, URL)
74-
}
77+
defer gzr.Close()
78+
r = gzr
7579
}
80+
81+
br := bufio.NewReaderSize(r, 1<<20) // read-ahead; does NOT cap JSON size
82+
dec := json.NewDecoder(br)
83+
dec.UseNumber()
84+
85+
// Output slice + appender (kept from your original design)
7686
itemType := l.Options.OutputType.Elem()
7787
xSlice := xunsafe.NewSlice(l.Options.OutputType)
78-
scanner := bufio.NewScanner(bytes.NewReader(data))
7988
response := reflect.New(l.Options.OutputType).Interface()
8089
appender := xSlice.Appender(xunsafe.AsPointer(response))
81-
scanner.Buffer(make([]byte, 1024*1024), 5*1024*1024)
82-
for scanner.Scan() {
83-
line := scanner.Bytes()
84-
if len(line) == 0 {
85-
continue
90+
91+
// Reject top-level arrays to keep the code simple (no streaming array parsing).
92+
first, err := peekFirstNonSpace(br)
93+
if err != nil {
94+
if errors.Is(err, io.EOF) {
95+
return response, nil // empty file -> empty slice
8696
}
87-
item := types.NewValue(itemType)
88-
err := json.Unmarshal(scanner.Bytes(), item)
89-
if err != nil {
90-
return nil, fmt.Errorf("invalid item: %w, %s", err, line)
97+
return nil, fmt.Errorf("read error: %w", err)
98+
}
99+
if first == '[' {
100+
return nil, fmt.Errorf("top-level JSON arrays are not supported; provide NDJSON (one object per line) or a single JSON object")
101+
}
102+
// Put the byte back so the decoder sees it.
103+
_ = br.UnreadByte()
104+
105+
// Decode one value per call: supports single object or NDJSON.
106+
for {
107+
item := types.NewValue(itemType) // pointer to zero value of element type
108+
if err := dec.Decode(item); err != nil {
109+
if errors.Is(err, io.EOF) {
110+
break
111+
}
112+
return nil, fmt.Errorf("invalid item: %w", err)
91113
}
92114
appender.Append(item)
93115
}
94-
return response, scanner.Err()
116+
117+
return response, nil
118+
}
119+
120+
// Reads and returns the first non-space byte without consuming input for the decoder.
121+
func peekFirstNonSpace(br *bufio.Reader) (byte, error) {
122+
for {
123+
b, err := br.ReadByte()
124+
if err != nil {
125+
return 0, err
126+
}
127+
if b == ' ' || b == '\n' || b == '\r' || b == '\t' {
128+
continue
129+
}
130+
return b, nil
131+
}
95132
}
96133

97134
func (*LoadDataProvider) New(ctx context.Context, opts ...handler.Option) (handler.Handler, error) {

0 commit comments

Comments
 (0)