22
22
23
23
import com .google .common .collect .ImmutableMap ;
24
24
import java .lang .reflect .Method ;
25
- import java .nio .file .Path ;
26
- import java .nio .file .Paths ;
27
25
import java .util .Map ;
26
+ import org .apache .hadoop .fs .Path ;
28
27
import org .apache .iceberg .catalog .TableIdentifier ;
29
28
import org .apache .iceberg .exceptions .ForbiddenException ;
30
29
import org .apache .iceberg .rest .RESTCatalog ;
31
30
import org .apache .iceberg .view .BaseView ;
32
31
import org .apache .iceberg .view .View ;
32
+ import org .apache .iceberg .view .ViewBuilder ;
33
33
import org .apache .iceberg .view .ViewCatalogTests ;
34
34
import org .apache .polaris .core .admin .model .Catalog ;
35
35
import org .apache .polaris .core .admin .model .CatalogProperties ;
45
45
import org .apache .polaris .service .it .env .PolarisApiEndpoints ;
46
46
import org .apache .polaris .service .it .env .PolarisClient ;
47
47
import org .apache .polaris .service .it .ext .PolarisIntegrationTestExtension ;
48
+ import org .assertj .core .api .AbstractBooleanAssert ;
49
+ import org .assertj .core .api .AbstractStringAssert ;
48
50
import org .assertj .core .api .Assertions ;
49
51
import org .assertj .core .api .Assumptions ;
50
52
import org .assertj .core .configuration .PreferredAssumptionException ;
55
57
import org .junit .jupiter .api .Test ;
56
58
import org .junit .jupiter .api .TestInfo ;
57
59
import org .junit .jupiter .api .extension .ExtendWith ;
58
- import org .junit .jupiter .api .io .TempDir ;
59
60
60
61
/**
61
62
* Import the full core Iceberg catalog tests by hitting the REST service via the RESTCatalog
@@ -86,6 +87,8 @@ public abstract class PolarisRestCatalogViewIntegrationBase extends ViewCatalogT
86
87
private static PolarisApiEndpoints endpoints ;
87
88
private static PolarisClient client ;
88
89
private static ManagementApi managementApi ;
90
+ protected static final String POLARIS_IT_SUBDIR = "polaris_it" ;
91
+ protected static final String POLARIS_IT_CUSTOM_SUBDIR = "polaris_it_custom" ;
89
92
90
93
private RESTCatalog restCatalog ;
91
94
@@ -104,8 +107,6 @@ static void close() throws Exception {
104
107
105
108
@ BeforeEach
106
109
public void before (TestInfo testInfo ) {
107
- Assumptions .assumeThat (shouldSkip ()).isFalse ();
108
-
109
110
String principalName = client .newEntityName ("snowman-rest" );
110
111
String principalRoleName = client .newEntityName ("rest-admin" );
111
112
PrincipalWithCredentials principalCredentials =
@@ -157,12 +158,6 @@ public void cleanUp() {
157
158
*/
158
159
protected abstract StorageConfigInfo getStorageConfigInfo ();
159
160
160
- /**
161
- * @return Whether the tests should be skipped, for example due to environment variables not being
162
- * specified.
163
- */
164
- protected abstract boolean shouldSkip ();
165
-
166
161
@ Override
167
162
protected RESTCatalog catalog () {
168
163
return restCatalog ;
@@ -188,33 +183,84 @@ protected boolean overridesRequestedLocation() {
188
183
return true ;
189
184
}
190
185
191
- @ Override
186
+ protected String getCustomMetadataLocationDir () {
187
+ return "" ;
188
+ }
189
+
192
190
@ Test
191
+ @ Override
193
192
public void createViewWithCustomMetadataLocation () {
194
- Assertions .assertThatThrownBy (super ::createViewWithCustomMetadataLocation )
193
+ TableIdentifier identifier = TableIdentifier .of ("ns" , "view" );
194
+ String baseLocation = catalog ().properties ().get (CatalogEntity .DEFAULT_BASE_LOCATION_KEY );
195
+ if (this .requiresNamespaceCreate ()) {
196
+ // Use the default baseLocation of the catalog. No "write.metadata.path" set.
197
+ catalog ().createNamespace (identifier .namespace ());
198
+ }
199
+
200
+ // Negative test, we cannot create views outside of base location
201
+ ((AbstractBooleanAssert )
202
+ Assertions .assertThat (this .catalog ().viewExists (identifier ))
203
+ .as ("View should not exist" , new Object [0 ]))
204
+ .isFalse ();
205
+ ViewBuilder viewBuilder =
206
+ catalog ()
207
+ .buildView (identifier )
208
+ .withSchema (SCHEMA )
209
+ .withDefaultNamespace (identifier .namespace ())
210
+ .withDefaultCatalog (catalog ().name ())
211
+ .withQuery ("spark" , "select * from ns.tbl" )
212
+ .withProperty (
213
+ IcebergTableLikeEntity .USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY ,
214
+ getCustomMetadataLocationDir ())
215
+ .withLocation (baseLocation );
216
+ Assertions .assertThatThrownBy (viewBuilder ::create )
195
217
.isInstanceOf (ForbiddenException .class )
196
218
.hasMessageContaining ("Forbidden: Invalid locations" );
219
+
220
+ // Positive, we can create views in the default base location's subdirectory
221
+ String baseViewLocation = catalog ().properties ().get (CatalogEntity .DEFAULT_BASE_LOCATION_KEY );
222
+ String baseCustomWriteMetadataLocation = baseViewLocation + "/custom_location" ;
223
+ View view =
224
+ this .catalog ()
225
+ .buildView (identifier )
226
+ .withSchema (SCHEMA )
227
+ .withDefaultNamespace (identifier .namespace ())
228
+ .withDefaultCatalog (this .catalog ().name ())
229
+ .withQuery ("spark" , "select * from ns.tbl" )
230
+ .withProperty ("write.metadata.path" , baseCustomWriteMetadataLocation )
231
+ .withLocation (baseViewLocation )
232
+ .create ();
233
+ Assertions .assertThat (view ).isNotNull ();
234
+ ((AbstractBooleanAssert )
235
+ Assertions .assertThat (this .catalog ().viewExists (identifier ))
236
+ .as ("View should exist" , new Object [0 ]))
237
+ .isTrue ();
238
+ Assertions .assertThat (view .properties ())
239
+ .containsEntry ("write.metadata.path" , baseCustomWriteMetadataLocation );
240
+ ((AbstractStringAssert )
241
+ Assertions .assertThat (((BaseView ) view ).operations ().current ().metadataFileLocation ())
242
+ .isNotNull ())
243
+ .startsWith (new Path (baseCustomWriteMetadataLocation ).toString ());
197
244
}
198
245
199
246
@ Test
200
- public void createViewWithCustomMetadataLocationUsingPolaris ( @ TempDir Path tempDir ) {
247
+ public void createViewWithCustomMetadataLocationInheritedFromNamespace ( ) {
201
248
TableIdentifier identifier = TableIdentifier .of ("ns" , "view" );
202
-
203
- String location = Paths . get ( tempDir . toUri (). toString ()). toString () ;
204
- String customLocation = Paths . get ( tempDir . toUri (). toString (), " custom-location" ). toString () ;
205
- String customLocation2 = Paths . get ( tempDir . toUri (). toString (), " custom-location2" ). toString () ;
206
- String customLocationChild =
207
- Paths . get ( tempDir . toUri (). toString (), "custom-location/child" ). toString ();
208
-
209
- catalog ()
210
- . createNamespace (
211
- identifier . namespace (),
212
- ImmutableMap . of (
213
- IcebergTableLikeEntity . USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY , location ));
214
-
249
+ String viewBaseLocation = getCustomMetadataLocationDir ();
250
+ String customWriteMetadataLocation = viewBaseLocation + "/custom-location" ;
251
+ String customWriteMetadataLocation2 = viewBaseLocation + "/ custom-location2" ;
252
+ String customWriteMetadataLocationChild = viewBaseLocation + "/ custom-location/child" ;
253
+ if ( this . requiresNamespaceCreate ()) {
254
+ // Views can inherit the namespace's "write.metadata.path" setting in Polaris.
255
+ catalog ()
256
+ . createNamespace (
257
+ identifier . namespace (),
258
+ ImmutableMap . of (
259
+ IcebergTableLikeEntity . USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY ,
260
+ viewBaseLocation ));
261
+ }
215
262
Assertions .assertThat (catalog ().viewExists (identifier )).as ("View should not exist" ).isFalse ();
216
-
217
- // CAN create a view with a custom metadata location `baseLocation/customLocation`,
263
+ // CAN create a view with a custom metadata location `viewLocation/customLocation`,
218
264
// as long as the location is within the parent namespace's `write.metadata.path=baseLocation`
219
265
View view =
220
266
catalog ()
@@ -224,17 +270,17 @@ public void createViewWithCustomMetadataLocationUsingPolaris(@TempDir Path tempD
224
270
.withDefaultCatalog (catalog ().name ())
225
271
.withQuery ("spark" , "select * from ns.tbl" )
226
272
.withProperty (
227
- IcebergTableLikeEntity .USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY , customLocation )
228
- .withLocation (location )
273
+ IcebergTableLikeEntity .USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY ,
274
+ customWriteMetadataLocation )
275
+ .withLocation (viewBaseLocation )
229
276
.create ();
230
-
231
277
Assertions .assertThat (view ).isNotNull ();
232
278
Assertions .assertThat (catalog ().viewExists (identifier )).as ("View should exist" ).isTrue ();
233
- Assertions .assertThat (view .properties ()).containsEntry ("write.metadata.path" , customLocation );
279
+ Assertions .assertThat (view .properties ())
280
+ .containsEntry ("write.metadata.path" , customWriteMetadataLocation );
234
281
Assertions .assertThat (((BaseView ) view ).operations ().current ().metadataFileLocation ())
235
282
.isNotNull ()
236
- .startsWith (customLocation );
237
-
283
+ .startsWith (customWriteMetadataLocation );
238
284
// CANNOT update the view with a new metadata location `baseLocation/customLocation2`,
239
285
// even though the new location is still under the parent namespace's
240
286
// `write.metadata.path=baseLocation`.
@@ -245,11 +291,10 @@ public void createViewWithCustomMetadataLocationUsingPolaris(@TempDir Path tempD
245
291
.updateProperties ()
246
292
.set (
247
293
IcebergTableLikeEntity .USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY ,
248
- customLocation2 )
294
+ customWriteMetadataLocation2 )
249
295
.commit ())
250
296
.isInstanceOf (ForbiddenException .class )
251
297
.hasMessageContaining ("Forbidden: Invalid locations" );
252
-
253
298
// CANNOT update the view with a child metadata location `baseLocation/customLocation/child`,
254
299
// even though it is a subpath of the original view's
255
300
// `write.metadata.path=baseLocation/customLocation`.
@@ -260,7 +305,7 @@ public void createViewWithCustomMetadataLocationUsingPolaris(@TempDir Path tempD
260
305
.updateProperties ()
261
306
.set (
262
307
IcebergTableLikeEntity .USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY ,
263
- customLocationChild )
308
+ customWriteMetadataLocationChild )
264
309
.commit ())
265
310
.isInstanceOf (ForbiddenException .class )
266
311
.hasMessageContaining ("Forbidden: Invalid locations" );
0 commit comments