22//!
33//! This module provides a type-safe API for creating Delta tables.
44//! Use the [`create_table`] function to get a [`CreateTableTransactionBuilder`] that can be
5- //! configured before building the [`Transaction`].
5+ //! configured with table properties and other options before building the [`Transaction`].
6+ //!
7+ //! # Example
8+ //!
9+ //! ```rust,no_run
10+ //! use delta_kernel::transaction::create_table::create_table;
11+ //! use delta_kernel::schema::{StructType, StructField, DataType};
12+ //! use delta_kernel::committer::FileSystemCommitter;
13+ //! use std::sync::Arc;
14+ //! # use delta_kernel::Engine;
15+ //! # fn example(engine: &dyn Engine) -> delta_kernel::DeltaResult<()> {
16+ //!
17+ //! let schema = Arc::new(StructType::try_new(vec![
18+ //! StructField::new("id", DataType::INTEGER, false),
19+ //! ])?);
20+ //!
21+ //! let result = create_table("/path/to/table", schema, "MyApp/1.0")
22+ //! .with_table_properties([("myapp.version", "1.0")])
23+ //! .build(engine, Box::new(FileSystemCommitter::new()))?
24+ //! .commit(engine)?;
25+ //! # Ok(())
26+ //! # }
27+ //! ```
628
729// Allow `pub` items in this module even though the module itself may be `pub(crate)`.
830// The module visibility controls external access; items are `pub` for use within the crate
@@ -20,11 +42,25 @@ use crate::log_segment::LogSegment;
2042use crate :: schema:: SchemaRef ;
2143use crate :: snapshot:: Snapshot ;
2244use crate :: table_configuration:: TableConfiguration ;
23- use crate :: table_features:: { TABLE_FEATURES_MIN_READER_VERSION , TABLE_FEATURES_MIN_WRITER_VERSION } ;
45+ use crate :: table_features:: {
46+ SET_TABLE_FEATURE_SUPPORTED_PREFIX , TABLE_FEATURES_MIN_READER_VERSION ,
47+ TABLE_FEATURES_MIN_WRITER_VERSION ,
48+ } ;
49+ use crate :: table_properties:: DELTA_PROPERTY_PREFIX ;
2450use crate :: transaction:: Transaction ;
2551use crate :: utils:: { current_time_ms, try_parse_uri} ;
2652use crate :: { DeltaResult , Engine , Error , StorageHandler , PRE_COMMIT_VERSION } ;
2753
54+ /// Properties that are allowed to be set during create table.
55+ /// This list will expand as more features are supported (e.g., column mapping, clustering).
56+ /// The allow list will be deprecated once auto feature enablement is implemented
57+ /// like the Java Kernel.
58+ const ALLOWED_DELTA_PROPERTIES : & [ & str ] = & [
59+ // Empty for now - will add properties as features are implemented:
60+ // - "delta.columnMapping.mode" (for column mapping)
61+ // - etc.
62+ ] ;
63+
2864/// Ensures that no Delta table exists at the given path.
2965///
3066/// This function checks the `_delta_log` directory to determine if a table already exists.
@@ -78,6 +114,35 @@ fn ensure_table_does_not_exist(
78114 }
79115}
80116
117+ /// Validates that table properties are allowed during CREATE TABLE.
118+ ///
119+ /// This function enforces an allow list for delta properties:
120+ /// - Feature override properties (`delta.feature.*`) are never allowed
121+ /// - Delta properties (`delta.*`) must be on the allow list
122+ /// - Non-delta properties (user/application properties) are always allowed
123+ fn validate_table_properties ( properties : & HashMap < String , String > ) -> DeltaResult < ( ) > {
124+ for key in properties. keys ( ) {
125+ // Block all delta.feature.* properties (feature override properties)
126+ if key. starts_with ( SET_TABLE_FEATURE_SUPPORTED_PREFIX ) {
127+ return Err ( Error :: generic ( format ! (
128+ "Setting feature override property '{}' is not supported during CREATE TABLE" ,
129+ key
130+ ) ) ) ;
131+ }
132+ // For delta.* properties, check against allow list
133+ if key. starts_with ( DELTA_PROPERTY_PREFIX )
134+ && !ALLOWED_DELTA_PROPERTIES . contains ( & key. as_str ( ) )
135+ {
136+ return Err ( Error :: generic ( format ! (
137+ "Setting delta property '{}' is not supported during CREATE TABLE" ,
138+ key
139+ ) ) ) ;
140+ }
141+ // Non-delta properties (user/application properties) are always allowed
142+ }
143+ Ok ( ( ) )
144+ }
145+
81146/// Creates a builder for creating a new Delta table.
82147///
83148/// This function returns a [`CreateTableTransactionBuilder`] that can be configured with table
@@ -150,12 +215,55 @@ impl CreateTableTransactionBuilder {
150215 }
151216 }
152217
218+ /// Sets table properties for the new Delta table.
219+ ///
220+ /// Custom application properties (those not starting with `delta.`) are always allowed.
221+ /// Delta properties (`delta.*`) are validated against an allow list during [`build()`].
222+ /// Feature flags (`delta.feature.*`) are not supported during CREATE TABLE.
223+ ///
224+ /// This method can be called multiple times. If a property key already exists from a
225+ /// previous call, the new value will overwrite the old one.
226+ ///
227+ /// # Arguments
228+ ///
229+ /// * `properties` - A map of table property names to their values
230+ ///
231+ /// # Example
232+ ///
233+ /// ```rust,no_run
234+ /// # use delta_kernel::transaction::create_table::create_table;
235+ /// # use delta_kernel::schema::{StructType, DataType, StructField};
236+ /// # use std::sync::Arc;
237+ /// # fn example() -> delta_kernel::DeltaResult<()> {
238+ /// # let schema = Arc::new(StructType::try_new(vec![StructField::new("id", DataType::INTEGER, false)])?);
239+ /// let builder = create_table("/path/to/table", schema, "MyApp/1.0")
240+ /// .with_table_properties([
241+ /// ("myapp.version", "1.0"),
242+ /// ("myapp.author", "test"),
243+ /// ]);
244+ /// # Ok(())
245+ /// # }
246+ /// ```
247+ ///
248+ /// [`build()`]: CreateTableTransactionBuilder::build
249+ pub fn with_table_properties < I , K , V > ( mut self , properties : I ) -> Self
250+ where
251+ I : IntoIterator < Item = ( K , V ) > ,
252+ K : Into < String > ,
253+ V : Into < String > ,
254+ {
255+ self . table_properties
256+ . extend ( properties. into_iter ( ) . map ( |( k, v) | ( k. into ( ) , v. into ( ) ) ) ) ;
257+ self
258+ }
259+
153260 /// Builds a [`Transaction`] that can be committed to create the table.
154261 ///
155262 /// This method performs validation:
156263 /// - Checks that the table path is valid
157264 /// - Verifies the table doesn't already exist
158265 /// - Validates the schema is non-empty
266+ /// - Validates table properties against the allow list
159267 ///
160268 /// # Arguments
161269 ///
@@ -168,6 +276,7 @@ impl CreateTableTransactionBuilder {
168276 /// - The table path is invalid
169277 /// - A table already exists at the given path
170278 /// - The schema is empty
279+ /// - Unsupported delta properties or feature flags are specified
171280 pub fn build (
172281 self ,
173282 engine : & dyn Engine ,
@@ -181,6 +290,9 @@ impl CreateTableTransactionBuilder {
181290 return Err ( Error :: generic ( "Schema cannot be empty" ) ) ;
182291 }
183292
293+ // Validate table properties against allow list
294+ validate_table_properties ( & self . table_properties ) ?;
295+
184296 // Check if table already exists by looking for _delta_log directory
185297 let delta_log_url = table_url. join ( "_delta_log/" ) ?;
186298 let storage = engine. storage_handler ( ) ;
@@ -207,13 +319,6 @@ impl CreateTableTransactionBuilder {
207319 // Create pre-commit snapshot from protocol/metadata
208320 let log_root = table_url. join ( "_delta_log/" ) ?;
209321 let log_segment = LogSegment :: for_pre_commit ( log_root) ;
210-
211- // We validate that the table properties are empty. Supported
212- // table properties for create table will be allowlisted in the future.
213- assert ! (
214- metadata. configuration( ) . is_empty( ) ,
215- "Base create table API does not support table properties"
216- ) ;
217322 let table_configuration =
218323 TableConfiguration :: try_new ( metadata, protocol, table_url, PRE_COMMIT_VERSION ) ?;
219324
@@ -231,6 +336,7 @@ impl CreateTableTransactionBuilder {
231336mod tests {
232337 use super :: * ;
233338 use crate :: schema:: { DataType , StructField , StructType } ;
339+ use crate :: utils:: test_utils:: assert_result_error_with_message;
234340 use std:: sync:: Arc ;
235341
236342 fn test_schema ( ) -> SchemaRef {
@@ -263,4 +369,79 @@ mod tests {
263369
264370 assert_eq ! ( builder. path, "/path/to/table/nested" ) ;
265371 }
372+
373+ #[ test]
374+ fn test_with_table_properties ( ) {
375+ let schema = test_schema ( ) ;
376+
377+ let builder = CreateTableTransactionBuilder :: new ( "/path/to/table" , schema, "TestApp/1.0" )
378+ . with_table_properties ( [ ( "key1" , "value1" ) ] ) ;
379+
380+ assert_eq ! (
381+ builder. table_properties. get( "key1" ) ,
382+ Some ( & "value1" . to_string( ) )
383+ ) ;
384+ }
385+
386+ #[ test]
387+ fn test_with_multiple_table_properties ( ) {
388+ let schema = test_schema ( ) ;
389+
390+ let builder = CreateTableTransactionBuilder :: new ( "/path/to/table" , schema, "TestApp/1.0" )
391+ . with_table_properties ( [ ( "key1" , "value1" ) ] )
392+ . with_table_properties ( [ ( "key2" , "value2" ) ] ) ;
393+
394+ assert_eq ! (
395+ builder. table_properties. get( "key1" ) ,
396+ Some ( & "value1" . to_string( ) )
397+ ) ;
398+ assert_eq ! (
399+ builder. table_properties. get( "key2" ) ,
400+ Some ( & "value2" . to_string( ) )
401+ ) ;
402+ }
403+
404+ #[ test]
405+ fn test_validate_supported_properties ( ) {
406+ // Empty properties are allowed
407+ let properties = HashMap :: new ( ) ;
408+ assert ! ( validate_table_properties( & properties) . is_ok( ) ) ;
409+
410+ // User/application properties are allowed
411+ let mut properties = HashMap :: new ( ) ;
412+ properties. insert ( "myapp.version" . to_string ( ) , "1.0" . to_string ( ) ) ;
413+ properties. insert ( "custom.setting" . to_string ( ) , "value" . to_string ( ) ) ;
414+ assert ! ( validate_table_properties( & properties) . is_ok( ) ) ;
415+ }
416+
417+ #[ test]
418+ fn test_validate_unsupported_properties ( ) {
419+ // Delta properties not on allow list are rejected
420+ let mut properties = HashMap :: new ( ) ;
421+ properties. insert ( "delta.enableChangeDataFeed" . to_string ( ) , "true" . to_string ( ) ) ;
422+ assert_result_error_with_message (
423+ validate_table_properties ( & properties) ,
424+ "Setting delta property 'delta.enableChangeDataFeed' is not supported" ,
425+ ) ;
426+
427+ // Feature override properties are rejected
428+ let mut properties = HashMap :: new ( ) ;
429+ properties. insert (
430+ "delta.feature.domainMetadata" . to_string ( ) ,
431+ "supported" . to_string ( ) ,
432+ ) ;
433+ assert_result_error_with_message (
434+ validate_table_properties ( & properties) ,
435+ "Setting feature override property 'delta.feature.domainMetadata' is not supported" ,
436+ ) ;
437+
438+ // Mixed properties with unsupported delta property are rejected
439+ let mut properties = HashMap :: new ( ) ;
440+ properties. insert ( "myapp.version" . to_string ( ) , "1.0" . to_string ( ) ) ;
441+ properties. insert ( "delta.appendOnly" . to_string ( ) , "true" . to_string ( ) ) ;
442+ assert_result_error_with_message (
443+ validate_table_properties ( & properties) ,
444+ "Setting delta property 'delta.appendOnly' is not supported" ,
445+ ) ;
446+ }
266447}
0 commit comments