This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git
The following commit(s) were added to refs/heads/main by this push:
new 32add8eb2 perf(fileuri): skip URL parsing for plain local paths (#2278)
32add8eb2 is described below
commit 32add8eb24fb84a0263d00c870da441f482c049f
Author: Minh Vu <[email protected]>
AuthorDate: Fri Oct 23 19:00:18 2026 +0200
perf(fileuri): skip URL parsing for plain local paths (#2278)
* perf(fileuri): bypass general URL parser for plain local paths
Signed-off-by: Minh Vu <[email protected]>
* style(fileuri): satisfy Go lint in fast-path tests
* style(fileuri): satisfy Go lint in fast-path tests
* style(fileuri): satisfy Go lint in fast-path tests
---------
Signed-off-by: Minh Vu <[email protected]>
---
internal/fileuri/file_uri.go | 28 ++++++++
internal/fileuri/file_uri_fastpath_bench_test.go | 53 +++++++++++++++
internal/fileuri/file_uri_fastpath_test.go | 84 ++++++++++++++++++++++++
3 files changed, 165 insertions(+)
diff --git a/internal/fileuri/file_uri.go b/internal/fileuri/file_uri.go
index c4f5bf93f..6e97891cb 100644
--- a/internal/fileuri/file_uri.go
+++ b/internal/fileuri/file_uri.go
@@ -35,6 +35,15 @@ type FileURI struct {
// Parse parses hierarchical and opaque file URIs using the same lexical rules
// for every FileIO consumer.
func Parse(name string) (FileURI, error) {
+ // Common hierarchical local file URIs have no URL components to decode.
+ // Escapes, delimiters, authorities and unusual characters use net/url.
+ if strings.HasPrefix(name, "file:///") {
+ path := name[len("file://"):]
+ if isPlainFilePath(path) {
+ return FileURI{parsed: url.URL{Scheme: "file", Path:
path}, path: path}, nil
+ }
+ }
+
parsed, err := url.Parse(name)
if err != nil {
return FileURI{}, err
@@ -54,6 +63,25 @@ func Parse(name string) (FileURI, error) {
return FileURI{parsed: *parsed, path: localPath}, nil
}
+// isPlainFilePath accepts a conservative path alphabet that net/url leaves
+// untouched. Keeping special characters on the fallback preserves errors,
+// escaping, query strings, fragments and opaque URL semantics.
+func isPlainFilePath(path string) bool {
+ for i := range len(path) {
+ c := path[i]
+ if (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '/' || c == '-' || c == '_' || c == '.' || c ==
'~' {
+ continue
+ }
+
+ return false
+ }
+
+ return true
+}
+
// Host returns the URI authority without changing its spelling.
func (f FileURI) Host() string {
return f.parsed.Host
diff --git a/internal/fileuri/file_uri_fastpath_bench_test.go
b/internal/fileuri/file_uri_fastpath_bench_test.go
new file mode 100644
index 000000000..b976a15e7
--- /dev/null
+++ b/internal/fileuri/file_uri_fastpath_bench_test.go
@@ -0,0 +1,53 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 fileuri
+
+import (
+ "strings"
+ "testing"
+)
+
+var parsedFileURISink FileURI
+
+// BenchmarkParseFileURI covers simple local paths and the fallback forms used
+// by FileIO. Compare the baseline and candidate commits with the same command.
+func BenchmarkParseFileURI(b *testing.B) {
+ for _, tc := range []struct {
+ name string
+ uri string
+ }{
+ {"short-local", "file:///tmp/data.parquet"},
+ {"warehouse-path",
"file:///srv/warehouse/database/table/data/2026/10/part-00000001.parquet"},
+ {"long-local", "file:///srv/" +
strings.Repeat("partition_2026/", 20) + "data.parquet"},
+ {"escaped-local", "file:///srv/warehouse/data/a%20b.parquet"},
+ {"authority", "file://localhost/a/b.parquet"},
+ {"windows-drive", "file:///C:/Warehouse/data.parquet"},
+ {"opaque", "file:C:/Warehouse/data.parquet"},
+ } {
+ b.Run(tc.name, func(b *testing.B) {
+ b.ReportAllocs()
+ for range b.N {
+ parsed, err := Parse(tc.uri)
+ if err != nil {
+ b.Fatal(err)
+ }
+ parsedFileURISink = parsed
+ }
+ })
+ }
+}
diff --git a/internal/fileuri/file_uri_fastpath_test.go
b/internal/fileuri/file_uri_fastpath_test.go
new file mode 100644
index 000000000..72244e787
--- /dev/null
+++ b/internal/fileuri/file_uri_fastpath_test.go
@@ -0,0 +1,84 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 fileuri
+
+import (
+ "net/url"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+// Compare all URL fields, not just the decoded path. JoinPath depends on the
+// original hierarchical/opaque URL representation.
+func TestParseFileURIEquivalentToURLParser(t *testing.T) {
+ for _, uri := range []string{
+ "file:///", "file:////", "file:///tmp/data.parquet",
+ "file:///srv/data/a-b_1.2~3", "file:///srv//data/../file",
+ "file:///" + strings.Repeat("abc_0123456789/", 20),
+ "file:///C:/Warehouse/data.parquet", "file:///a%20b",
+ "file:///data?version=1", "file:///data#fragment",
+ "file:///data with spaces", "file:///café",
"file:///data\\next",
+ "file:///data\x00nul", "file://localhost/a.parquet",
+ "file:C:/Warehouse/data.parquet", "file:///a%ZZ",
+ "FILE:///tmp/data.parquet", "s3:///tmp/data.parquet",
+ } {
+ got, gotErr := Parse(uri)
+ want, wantErr := url.Parse(uri)
+ valid := wantErr == nil && strings.EqualFold(want.Scheme,
"file")
+ if (gotErr == nil) != valid {
+ t.Errorf("Parse(%q): got error %v; url.Parse error %v;
valid=%v", uri, gotErr, wantErr, valid)
+
+ continue
+ }
+ if !valid {
+ continue
+ }
+ if !reflect.DeepEqual(got.parsed, *want) {
+ t.Errorf("Parse(%q): URL=%#v, want %#v", uri,
got.parsed, *want)
+ }
+
+ wantPath := want.Path
+ if want.Opaque != "" {
+ var err error
+ wantPath, err = url.PathUnescape(want.Opaque)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ if got.LocalPath(false) != wantPath {
+ t.Errorf("Parse(%q): path=%q, want %q", uri, got.path,
wantPath)
+ }
+ if want.Opaque == "" && got.JoinPath("metadata", "v1.json") !=
want.JoinPath("metadata", "v1.json").String() {
+ t.Errorf("Parse(%q): JoinPath differs from net/url",
uri)
+ }
+ }
+}
+
+func TestPlainFilePathAlphabet(t *testing.T) {
+ const safe =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/-_.~"
+ const unsafe = "%?#\\ :@!$\x00é"
+ if !isPlainFilePath(safe) {
+ t.Fatal("simple URI alphabet rejected")
+ }
+ for _, c := range unsafe {
+ if isPlainFilePath("/tmp/" + string(c)) {
+ t.Errorf("unsupported character %q accepted", c)
+ }
+ }
+}