3939 CONSTRAINT_RECORDS_READ ,
4040 CatalogCapabilities ,
4141)
42+ from vgi .catalog .catalog_interface import SerializedSchema
4243from vgi .catalog .column_metadata import column_metadata
4344from vgi .catalog .constraints import (
4445 ConstraintDefinition ,
5859from vgi .column_path import column_definition , column_path_key , replace_column_field , resolve_column_path
5960from vgi .worker import Worker
6061
61- from .table_layout_reference import TABLE_PROFILES , TableLayoutCatalog , _table_key , _validate_expression
62+ from .expression_reference import validate_expression as _validate_expression
63+ from .table_layout_reference import TABLE_PROFILES , TableLayoutCatalog , _table_key
64+ from .typing_helpers import present
6265
6366COLUMN_PROFILES = CatalogCapabilities (
6467 read = sorted ([* TABLE_PROFILES .read , CONSTRAINT_RECORDS_READ ]),
@@ -86,7 +89,7 @@ def _eval(
8689 path : list [str ],
8790 schema : pa .Schema | None = None ,
8891 rows : list [dict [str , Any ]] | None = None ,
89- ) -> pa .Array :
92+ ) -> pa .Array [ Any ] :
9093 expression .validate_owner (path )
9194 with duckdb .connect (config = {"enable_external_access" : False }) as connection :
9295 connection .execute ("SET TimeZone='UTC'" )
@@ -176,15 +179,15 @@ class ColumnPathsCatalog(TableLayoutCatalog):
176179 profiles = COLUMN_PROFILES
177180
178181 def table_create (self , ** kwargs : Any ) -> None :
179- _fields (pa .ipc .read_schema (pa .BufferReader (kwargs ["columns" ])), kwargs ["schema_path" ])
182+ _fields (pa .ipc .read_schema (pa .py_buffer (kwargs ["columns" ])), kwargs ["schema_path" ])
180183 super ().table_create (** kwargs )
181184
182185 def _create_constraints (self , args : dict [str , Any ]) -> tuple [list [bytes ], bytes | None ]:
183186 table = CatalogObjectRef (kind = "table" , schema_path = args ["schema_path" ], name = args ["name" ])
184187 definitions = constraint_definitions (args ["constraint_definitions" ], table )
185188 if any (definition .kind != ConstraintKind .NOT_NULL for definition in definitions ):
186189 raise NotImplementedError ("column reference only enforces NOT_NULL constraints" )
187- validate_constraint_schema (pa .ipc .read_schema (pa .BufferReader (args ["columns" ])), definitions )
190+ validate_constraint_schema (pa .ipc .read_schema (pa .py_buffer (args ["columns" ])), definitions )
188191 return [
189192 self ._not_null_info (table , definition ).serialize_to_bytes () for definition in definitions
190193 ], uuid .uuid4 ().bytes
@@ -201,7 +204,7 @@ def _not_null_info(table: CatalogObjectRef, definition: ConstraintDefinition) ->
201204 )
202205
203206 def _changed_constraints (
204- self , info : TableInfo , schema : pa .Schema , operation : str , path : list [str ], replacement : pa .Field | None
207+ self , info : TableInfo , schema : pa .Schema , operation : str , path : list [str ], replacement : pa .Field [ Any ] | None
205208 ) -> list [bytes ]:
206209 existing = {}
207210 for constraint in info .constraint_infos ():
@@ -234,7 +237,7 @@ def collect(fields: Any, prefix: list[str]) -> None:
234237 return result
235238
236239 def _arrange (self , rows : list [dict [str , Any ]], info : TableInfo ) -> list [dict [str , Any ]]:
237- schema = pa .ipc .read_schema (pa .BufferReader (info .columns ))
240+ schema = pa .ipc .read_schema (pa .py_buffer (info .columns ))
238241 # Resolve defaults from the same locked snapshot that stores the rows.
239242 # Reading table metadata before _change would race concurrent DDL.
240243 rows = [_defaulted (schema , row , info .schema_path ) for row in rows ]
@@ -250,7 +253,7 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
250253 version , data = self ._snapshot (db , args ["attach_opaque_data" ], args ["transaction_opaque_data" ])
251254 if key not in data :
252255 if args .get ("ignore_not_found" ):
253- return
256+ return None
254257 raise ValueError ("table does not exist" )
255258 info = self ._info (data [key ])
256259 request = args .get ("constraint_request" )
@@ -264,12 +267,14 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
264267 raise ValueError ("NOT_NULL constraint already exists" )
265268 operation = "not_null_set"
266269 else :
267- matches = [item for item in info .constraint_infos () if item .ref .key () == change .ref .key ()]
268- if len (matches ) != 1 or matches [0 ].definition .kind != ConstraintKind .NOT_NULL :
270+ constraint_matches = [
271+ item for item in info .constraint_infos () if item .ref .key () == change .ref .key ()
272+ ]
273+ if len (constraint_matches ) != 1 or constraint_matches [0 ].definition .kind != ConstraintKind .NOT_NULL :
269274 raise ValueError ("unknown NOT_NULL constraint identity" )
270- path = matches [0 ].definition .column_paths [0 ]
275+ path = constraint_matches [0 ].definition .column_paths [0 ]
271276 operation = "not_null_drop"
272- schema = pa .ipc .read_schema (pa .BufferReader (info .columns ))
277+ schema = pa .ipc .read_schema (pa .py_buffer (info .columns ))
273278 parent_fields = schema if len (path ) == 1 else resolve_column_path (schema , path [:- 1 ]).type
274279 if not isinstance (parent_fields , pa .Schema ) and not pa .types .is_struct (parent_fields ):
275280 raise ValueError ("column path has a non-struct parent" )
@@ -279,11 +284,11 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
279284 existing = matches [0 ] if matches else None
280285 if operation == "add" and existing is not None :
281286 if args .get ("if_column_not_exists" ):
282- return
287+ return None
283288 raise ValueError ("column already exists" )
284289 if operation != "add" and existing is None :
285290 if operation == "drop" and args .get ("if_column_exists" ):
286- return
291+ return None
287292 raise ValueError ("column does not exist" )
288293 if (
289294 operation in ("drop" , "rename" , "type" )
@@ -303,7 +308,7 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
303308 parent = _parent (row , path )
304309 if parent is not None :
305310 parent [path [- 1 ]] = (
306- pc .cast (_eval (default , info .schema_path ), replacement .type , safe = True )[0 ].as_py ()
311+ pc .cast (_eval (default , info .schema_path ), present ( replacement ) .type , safe = True )[0 ].as_py ()
307312 if default is not None
308313 else None
309314 )
@@ -327,7 +332,12 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
327332 # annotations when replacing the primitive physical type.
328333 metadata = dict (existing .metadata or {})
329334 metadata .update (target .metadata or {})
330- replacement = existing .with_type (target .type ).with_metadata (metadata or None )
335+ replacement = existing .with_type (target .type )
336+ replacement = (
337+ replacement .with_metadata ({k : v for k , v in metadata .items ()})
338+ if metadata
339+ else replacement .remove_metadata ()
340+ )
331341 _fields ([replacement ], info .schema_path )
332342 if args ["expression" ] is not None :
333343 # Bind before changes even if the table has no rows.
@@ -363,13 +373,17 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
363373 else :
364374 raise AssertionError (operation )
365375 assert replacement is not None
366- replacement = replacement .with_metadata (metadata or None )
376+ replacement = (
377+ replacement .with_metadata ({k : v for k , v in metadata .items ()})
378+ if metadata
379+ else replacement .remove_metadata ()
380+ )
367381 updated = replace_column_field (schema , path , replacement , add = operation == "add" )
368382 constraints = self ._changed_constraints (info , updated , operation , path , replacement )
369383 changed = not updated .equals (schema , check_metadata = True ) or constraints != info .constraints
370384 info = replace (
371385 info ,
372- columns = updated .serialize ().to_pybytes (),
386+ columns = SerializedSchema ( updated .serialize ().to_pybytes () ),
373387 constraints = constraints ,
374388 definition_revision = uuid .uuid4 ().bytes
375389 if changed and operation != "comment"
@@ -392,7 +406,9 @@ def _column_change(self, operation: str, args: dict[str, Any]) -> ConstraintSnap
392406 db .execute ("UPDATE transactions SET version=?, data=? WHERE id=?" , (version + 1 , json .dumps (data ), tx ))
393407 if request is not None :
394408 return ConstraintSnapshot (
395- table = request .table , definition_revision = info .definition_revision , constraints = info .constraints
409+ table = request .table ,
410+ definition_revision = present (info .definition_revision ),
411+ constraints = info .constraints ,
396412 )
397413 return None
398414
@@ -406,8 +422,8 @@ def table_constraints_apply(self, request: ConstraintsApplyRequest) -> Constrain
406422 if change .operation == ConstraintOperation .ADD :
407423 definition = change .definition
408424 if (
409- definition .kind != ConstraintKind .NOT_NULL
410- or definition .declared_name is not None
425+ present ( definition ) .kind != ConstraintKind .NOT_NULL
426+ or present ( definition ) .declared_name is not None
411427 or change .enforcement != ConstraintEnforcement .ENFORCED
412428 or change .validation != ConstraintValidation .ALL_ROWS
413429 ):
@@ -458,4 +474,9 @@ def table_column_comment_set(self, **kwargs: Any) -> None:
458474class ColumnPathsWorker (Worker ):
459475 """Expose the durable column reference through the normal protocol worker."""
460476
477+ def _get_catalog (self ) -> ColumnPathsCatalog :
478+ catalog = super ()._get_catalog ()
479+ assert isinstance (catalog , ColumnPathsCatalog )
480+ return catalog
481+
461482 catalog_interface = ColumnPathsCatalog
0 commit comments