laskoviymishka commented on code in PR #1280:
URL: https://github.com/apache/iceberg-go/pull/1280#discussion_r3467400129


##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {
+               if u.Opaque != "" {
+                       warehouse = u.Opaque
+               } else {
+                       warehouse = u.Path
+               }
 
-       if u.Opaque != "" {
-               warehouse = u.Opaque
-       } else {
-               warehouse = u.Path
-       }
+               if warehouse == "" || warehouse == "/" {
+                       return nil, errors.New("hadoop catalog: local 
filesystem requires a non-root warehouse path")
+               }
 
-       if warehouse == "" || warehouse == "/" {
-               return nil, errors.New("hadoop catalog requires a non-root 
warehouse path")
-       }
+               warehouse = strings.TrimRight(warehouse, "/")
 
-       warehouse = strings.TrimRight(warehouse, "/")
+               // Normalize to absolute path so the synthetic "location" 
property
+               // always produces a valid file:// URI.
+               absWarehouse, err := filepath.Abs(warehouse)
+               if err != nil {
+                       return nil, fmt.Errorf("hadoop catalog: failed to 
resolve absolute warehouse path: %w", err)
+               }
 
-       // Normalize to absolute path so the synthetic "location" property
-       // always produces a valid file:// URI.
-       absWarehouse, err := filepath.Abs(warehouse)
+               warehouse = absWarehouse
+       }
+
+       filesystem, err := io.LoadFS(context.Background(), nil, warehouse)

Review Comment:
   We're passing `nil` here, but the catalog's `props` are in scope and the 
cloud factories (`createS3Bucket`, `createGCSBucket`, `createAzureBucket`) read 
region, access key, secret, endpoint, etc. straight off the props map. With 
`nil` every lookup returns zero-value, so the FS falls back entirely to ambient 
SDK credentials and silently ignores anything the user set via 
`s3.access-key-id` / `s3.region` / `s3.endpoint`.
   
   It's also inconsistent with the table path — `io.LoadFSFunc(c.props, 
metaPath)` a few lines down passes the real props, so the catalog FS and table 
FS end up configured differently.
   
   `iceberg.Properties` is `map[string]string`, so this is just 
`io.LoadFS(context.Background(), props, warehouse)`. wdyt?



##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {

Review Comment:
   There's a subtle issue with the third branch that this control flow hides. 
When `isLocal` is false and `allowUnsafeCommits` is true, we fall through with 
`warehouse` left as the raw URI, e.g. `s3://bucket/wh`.
   
   Every path-building method — `namespaceToPath`, `tableToPath`, 
`metadataDir`, `metadataFilePath`, `versionHintPath`, `defaultTableLocation` — 
then calls `filepath.Join(c.warehouse, ...)`, and `filepath.Join` runs 
`filepath.Clean`, which collapses `s3://bucket` to `s3:/bucket`. So the 
`location` we write into table metadata comes out as `s3:/bucket/wh/...` — a 
single slash after the scheme — which Java, PyIceberg, and iceberg-rust all 
reject when they parse it as a URI.
   
   I'd make the join scheme-aware: `filepath.Join` for local, and `path.Join` 
(or `strings.TrimRight(base, "/") + "/" + ...`) preserving the 
`scheme://authority` prefix for remote. Probably worth a single `joinPath` 
helper that branches on `isLocal`.
   
   Also, while we're here — the `else if` after an unconditional `return` isn't 
idiomatic; dropping the `else` and using a plain `if isLocal {` would make the 
non-local fallthrough visible rather than implied.



##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {
+               if u.Opaque != "" {
+                       warehouse = u.Opaque
+               } else {
+                       warehouse = u.Path
+               }
 
-       if u.Opaque != "" {
-               warehouse = u.Opaque
-       } else {
-               warehouse = u.Path
-       }
+               if warehouse == "" || warehouse == "/" {
+                       return nil, errors.New("hadoop catalog: local 
filesystem requires a non-root warehouse path")
+               }
 
-       if warehouse == "" || warehouse == "/" {
-               return nil, errors.New("hadoop catalog requires a non-root 
warehouse path")
-       }
+               warehouse = strings.TrimRight(warehouse, "/")
 
-       warehouse = strings.TrimRight(warehouse, "/")
+               // Normalize to absolute path so the synthetic "location" 
property
+               // always produces a valid file:// URI.
+               absWarehouse, err := filepath.Abs(warehouse)
+               if err != nil {
+                       return nil, fmt.Errorf("hadoop catalog: failed to 
resolve absolute warehouse path: %w", err)
+               }
 
-       // Normalize to absolute path so the synthetic "location" property
-       // always produces a valid file:// URI.
-       absWarehouse, err := filepath.Abs(warehouse)
+               warehouse = absWarehouse
+       }
+
+       filesystem, err := io.LoadFS(context.Background(), nil, warehouse)

Review Comment:
   `context.Background()` is the only option here since `NewCatalog` has no 
`ctx` param, which is fine and out of scope to change. But the S3 factory does 
use the context for `config.LoadDefaultConfig` and the bucket handle, so it'd 
be good to leave a `// TODO: propagate caller context once NewCatalog accepts 
one` here as a marker for the follow-up.



##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {
+               if u.Opaque != "" {
+                       warehouse = u.Opaque
+               } else {
+                       warehouse = u.Path
+               }
 
-       if u.Opaque != "" {
-               warehouse = u.Opaque
-       } else {
-               warehouse = u.Path
-       }
+               if warehouse == "" || warehouse == "/" {
+                       return nil, errors.New("hadoop catalog: local 
filesystem requires a non-root warehouse path")
+               }
 
-       if warehouse == "" || warehouse == "/" {
-               return nil, errors.New("hadoop catalog requires a non-root 
warehouse path")
-       }
+               warehouse = strings.TrimRight(warehouse, "/")
 
-       warehouse = strings.TrimRight(warehouse, "/")
+               // Normalize to absolute path so the synthetic "location" 
property
+               // always produces a valid file:// URI.
+               absWarehouse, err := filepath.Abs(warehouse)
+               if err != nil {
+                       return nil, fmt.Errorf("hadoop catalog: failed to 
resolve absolute warehouse path: %w", err)
+               }
 
-       // Normalize to absolute path so the synthetic "location" property
-       // always produces a valid file:// URI.
-       absWarehouse, err := filepath.Abs(warehouse)
+               warehouse = absWarehouse
+       }
+
+       filesystem, err := io.LoadFS(context.Background(), nil, warehouse)
        if err != nil {
-               return nil, fmt.Errorf("hadoop catalog: failed to resolve 
absolute warehouse path: %w", err)
+               return nil, fmt.Errorf("hadoop catalog: failed to load 
filesystem: %w", err)
        }
 
-       warehouse = absWarehouse
+       hadoopFs, ok := filesystem.(HadoopCatalogFS)

Review Comment:
   Minor, but the message is a little wordy given the `hadoop catalog:` prefix 
— naming the interface directly is more actionable: `"hadoop catalog: %T does 
not implement HadoopCatalogFS"`.



##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {
+               if u.Opaque != "" {
+                       warehouse = u.Opaque
+               } else {
+                       warehouse = u.Path
+               }
 
-       if u.Opaque != "" {
-               warehouse = u.Opaque
-       } else {
-               warehouse = u.Path
-       }
+               if warehouse == "" || warehouse == "/" {
+                       return nil, errors.New("hadoop catalog: local 
filesystem requires a non-root warehouse path")
+               }
 
-       if warehouse == "" || warehouse == "/" {
-               return nil, errors.New("hadoop catalog requires a non-root 
warehouse path")
-       }
+               warehouse = strings.TrimRight(warehouse, "/")
 
-       warehouse = strings.TrimRight(warehouse, "/")
+               // Normalize to absolute path so the synthetic "location" 
property
+               // always produces a valid file:// URI.
+               absWarehouse, err := filepath.Abs(warehouse)
+               if err != nil {
+                       return nil, fmt.Errorf("hadoop catalog: failed to 
resolve absolute warehouse path: %w", err)
+               }
 
-       // Normalize to absolute path so the synthetic "location" property
-       // always produces a valid file:// URI.
-       absWarehouse, err := filepath.Abs(warehouse)
+               warehouse = absWarehouse
+       }
+
+       filesystem, err := io.LoadFS(context.Background(), nil, warehouse)
        if err != nil {
-               return nil, fmt.Errorf("hadoop catalog: failed to resolve 
absolute warehouse path: %w", err)
+               return nil, fmt.Errorf("hadoop catalog: failed to load 
filesystem: %w", err)
        }
 
-       warehouse = absWarehouse
+       hadoopFs, ok := filesystem.(HadoopCatalogFS)

Review Comment:
   I think this assertion can never succeed for a remote store today, which 
means the PR's headline capability isn't actually reachable.
   
   `HadoopCatalogFS` requires `Stat`, `Rename`, `RemoveAll`, `MkdirAll` on top 
of the read/write/list set. `blobFileIO` — what all the S3/GCS/Azure backends 
return — only implements `Open`, `Remove`, `Create`, `WriteFile`, `WalkDir`, 
`ReadFile`, `DeleteFiles`. So for any non-local scheme `LoadFS` succeeds, `ok` 
is false, and `NewCatalog` errors out even with `allow-unsafe-commits=true`. A 
user who correctly imports the gocloud backend sees "does not implement 
necessary functions" and reasonably reads it as a library bug.
   
   The hard part is `Rename` — it's fundamentally non-atomic on object stores, 
which is exactly why Java warns against S3-backed `HadoopCatalog`. So I'd 
either extend `blobFileIO` with the four methods (with `Rename` as copy+delete) 
before merging, or scope this PR as a preparatory step and say so in the 
description. Leaving the assertion with nothing that can satisfy it is the dead 
end I'd want to avoid. Thoughts?



##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {
+               if u.Opaque != "" {
+                       warehouse = u.Opaque
+               } else {
+                       warehouse = u.Path
+               }
 
-       if u.Opaque != "" {
-               warehouse = u.Opaque
-       } else {
-               warehouse = u.Path
-       }
+               if warehouse == "" || warehouse == "/" {
+                       return nil, errors.New("hadoop catalog: local 
filesystem requires a non-root warehouse path")
+               }
 
-       if warehouse == "" || warehouse == "/" {
-               return nil, errors.New("hadoop catalog requires a non-root 
warehouse path")
-       }
+               warehouse = strings.TrimRight(warehouse, "/")
 
-       warehouse = strings.TrimRight(warehouse, "/")
+               // Normalize to absolute path so the synthetic "location" 
property
+               // always produces a valid file:// URI.
+               absWarehouse, err := filepath.Abs(warehouse)
+               if err != nil {
+                       return nil, fmt.Errorf("hadoop catalog: failed to 
resolve absolute warehouse path: %w", err)
+               }
 
-       // Normalize to absolute path so the synthetic "location" property
-       // always produces a valid file:// URI.
-       absWarehouse, err := filepath.Abs(warehouse)
+               warehouse = absWarehouse
+       }
+
+       filesystem, err := io.LoadFS(context.Background(), nil, warehouse)
        if err != nil {
-               return nil, fmt.Errorf("hadoop catalog: failed to resolve 
absolute warehouse path: %w", err)
+               return nil, fmt.Errorf("hadoop catalog: failed to load 
filesystem: %w", err)
        }
 
-       warehouse = absWarehouse
+       hadoopFs, ok := filesystem.(HadoopCatalogFS)

Review Comment:
   Related, but worth its own note since it lives outside the diff: 
`LoadNamespaceProperties` builds the location property as `(&url.URL{Scheme: 
"file", Path: path}).String()` with a hardcoded `file://` scheme. Once a 
non-local warehouse is supported, an S3 namespace returns 
`file://s3:/bucket/wh/ns` — a `file://` URL wrapping the corrupted S3 path. I'd 
thread the actual warehouse scheme through there (or reuse the 
`namespaceToPath` result) as part of the same scheme-awareness fix.



##########
catalog/hadoop/hadoop.go:
##########
@@ -135,35 +135,45 @@ func NewCatalog(name, warehouse string, props 
iceberg.Properties) (*Catalog, err
 
        if !isLocal && !allowUnsafeCommits {
                return nil, fmt.Errorf("hadoop catalog: when using warehouse 
scheme %q, `allow-unsafe-commits` must be set to true", u.Scheme)
-       }
+       } else if isLocal {
+               if u.Opaque != "" {
+                       warehouse = u.Opaque
+               } else {
+                       warehouse = u.Path
+               }
 
-       if u.Opaque != "" {
-               warehouse = u.Opaque
-       } else {
-               warehouse = u.Path
-       }
+               if warehouse == "" || warehouse == "/" {
+                       return nil, errors.New("hadoop catalog: local 
filesystem requires a non-root warehouse path")
+               }
 
-       if warehouse == "" || warehouse == "/" {
-               return nil, errors.New("hadoop catalog requires a non-root 
warehouse path")
-       }
+               warehouse = strings.TrimRight(warehouse, "/")
 
-       warehouse = strings.TrimRight(warehouse, "/")
+               // Normalize to absolute path so the synthetic "location" 
property
+               // always produces a valid file:// URI.
+               absWarehouse, err := filepath.Abs(warehouse)
+               if err != nil {
+                       return nil, fmt.Errorf("hadoop catalog: failed to 
resolve absolute warehouse path: %w", err)
+               }
 
-       // Normalize to absolute path so the synthetic "location" property
-       // always produces a valid file:// URI.
-       absWarehouse, err := filepath.Abs(warehouse)
+               warehouse = absWarehouse
+       }
+
+       filesystem, err := io.LoadFS(context.Background(), nil, warehouse)
        if err != nil {
-               return nil, fmt.Errorf("hadoop catalog: failed to resolve 
absolute warehouse path: %w", err)
+               return nil, fmt.Errorf("hadoop catalog: failed to load 
filesystem: %w", err)
        }
 
-       warehouse = absWarehouse
+       hadoopFs, ok := filesystem.(HadoopCatalogFS)
+       if !ok {
+               return nil, fmt.Errorf("hadoop catalog: filesystem %T does not 
implement necessary functions to be used as a Hadoop catalog", filesystem)
+       }
 
        return &Catalog{
                name:      name,
                warehouse: warehouse,
                // for the time being, we default to localfs since there is not 
yet

Review Comment:
   This comment now contradicts the code right below it — we're no longer 
defaulting to localfs, the FS is loaded dynamically from the registry by 
scheme. I'd drop it or replace with something like "filesystem is resolved 
dynamically from the IO registry based on the warehouse scheme."



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to