@@ -485,6 +485,51 @@ def _create_table_from_columns(
485485 ** kwargs ,
486486 )
487487
488+ def _parse_partition_expressions (
489+ self , partitioned_by : t .List [exp .Expression ]
490+ ) -> t .Tuple [t .List [exp .Expression ], t .Optional [str ]]:
491+ """Parse partition expressions and extract partition kind and normalized columns.
492+
493+ Returns:
494+ Tuple of (normalized_partitioned_by, partition_kind)
495+ """
496+ parsed_partitioned_by : t .List [exp .Expression ] = []
497+ partition_kind : t .Optional [str ] = None
498+
499+ for expr in partitioned_by :
500+ try :
501+ # Handle Anonymous function calls like RANGE(col) or LIST(col)
502+ if isinstance (expr , exp .Anonymous ) and expr .this :
503+ func_name = str (expr .this ).upper ()
504+ if func_name in ("RANGE" , "LIST" ):
505+ partition_kind = func_name
506+ # Extract column expressions from function arguments
507+ for arg in expr .expressions :
508+ if isinstance (arg , exp .Column ):
509+ parsed_partitioned_by .append (arg )
510+ else :
511+ # Convert other expressions to columns if possible
512+ parsed_partitioned_by .append (exp .to_column (str (arg )))
513+ continue
514+
515+ # Handle literal strings like "RANGE(col)" or "LIST(col)"
516+ if isinstance (expr , exp .Literal ) and getattr (expr , "is_string" , False ):
517+ text = str (expr .this )
518+ match = re .match (r"^\s*(RANGE|LIST)\s*\((.*?)\)\s*$" , text , flags = re .IGNORECASE )
519+ if match :
520+ partition_kind = match .group (1 ).upper ()
521+ inner = match .group (2 )
522+ inner_cols = [c .strip ().strip ("`" ) for c in inner .split ("," ) if c .strip ()]
523+ for col in inner_cols :
524+ parsed_partitioned_by .append (exp .to_column (col ))
525+ continue
526+ except Exception :
527+ # If anything goes wrong, keep the original expr
528+ pass
529+ parsed_partitioned_by .append (expr )
530+
531+ return parsed_partitioned_by , partition_kind
532+
488533 def _build_partitioned_by_exp (
489534 self ,
490535 partitioned_by : t .List [exp .Expression ],
@@ -505,12 +550,11 @@ def _build_partitioned_by_exp(
505550
506551 Supports both RANGE and LIST partition syntaxes using sqlglot's doris dialect nodes.
507552 The partition kind is chosen by:
508- - kwargs["partition_kind"] if provided (expects 'RANGE' or 'LIST', case-insensitive)
553+ - inferred from partitioned_by expressions like 'RANGE(col) ' or 'LIST(col)'
509554 - otherwise inferred from the provided 'partitions' strings: if any contains 'VALUES IN' -> LIST; else RANGE.
510555 """
511556 partitions = kwargs .get ("partitions" )
512557 create_expressions = None
513- partition_kind : t .Optional [str ] = kwargs .get ("partition_kind" )
514558
515559 def to_raw_sql (expr : t .Union [exp .Literal , exp .Var , str , t .Any ]) -> exp .Var :
516560 # If it's a Literal, extract the string and wrap as Var (no quotes)
@@ -525,6 +569,9 @@ def to_raw_sql(expr: t.Union[exp.Literal, exp.Var, str, t.Any]) -> exp.Var:
525569 # Fallback: return as is
526570 return expr
527571
572+ # Parse partition kind and columns from partitioned_by expressions
573+ partitioned_by , partition_kind = self ._parse_partition_expressions (partitioned_by )
574+
528575 if partitions :
529576 if isinstance (partitions , exp .Tuple ):
530577 create_expressions = [
@@ -555,8 +602,8 @@ def to_raw_sql(expr: t.Union[exp.Literal, exp.Var, str, t.Any]) -> exp.Var:
555602 try :
556603 if is_list :
557604 return exp .PartitionByListProperty (
558- this = exp . Schema ( expressions = partitioned_by ) ,
559- partitions = create_expressions ,
605+ partition_expressions = partitioned_by ,
606+ create_expressions = create_expressions ,
560607 )
561608 return exp .PartitionByRangeProperty (
562609 partition_expressions = partitioned_by ,
@@ -800,46 +847,43 @@ def _parse_trigger_string(
800847 )
801848
802849 # Handle duplicate_key - only handle Tuple expressions or single Column expressions
850+ # Both tables and materialized views support duplicate keys in Doris
803851 duplicate_key = table_properties_copy .pop ("duplicate_key" , None )
804852 if duplicate_key is not None :
805- if not is_materialized_view :
806- if isinstance (duplicate_key , exp .Tuple ):
807- # Extract column names from Tuple expressions
808- column_names = []
809- for expr in duplicate_key .expressions :
810- if (
811- isinstance (expr , exp .Column )
812- and hasattr (expr , "this" )
813- and hasattr (expr .this , "this" )
814- ):
815- column_names .append (str (expr .this .this ))
816- elif hasattr (expr , "this" ):
817- column_names .append (str (expr .this ))
818- else :
819- column_names .append (str (expr ))
820- properties .append (
821- exp .DuplicateKeyProperty (
822- expressions = [exp .to_column (k ) for k in column_names ]
823- )
824- )
825- elif isinstance (duplicate_key , exp .Column ):
826- # Handle as single column
827- if hasattr (duplicate_key , "this" ) and hasattr (duplicate_key .this , "this" ):
828- column_name = str (duplicate_key .this .this )
853+ if isinstance (duplicate_key , exp .Tuple ):
854+ # Extract column names from Tuple expressions
855+ column_names = []
856+ for expr in duplicate_key .expressions :
857+ if (
858+ isinstance (expr , exp .Column )
859+ and hasattr (expr , "this" )
860+ and hasattr (expr .this , "this" )
861+ ):
862+ column_names .append (str (expr .this .this ))
863+ elif hasattr (expr , "this" ):
864+ column_names .append (str (expr .this ))
829865 else :
830- column_name = str (duplicate_key .this )
831- properties .append (
832- exp .DuplicateKeyProperty (expressions = [exp .to_column (column_name )])
833- )
834- elif isinstance (duplicate_key , exp .Literal ):
835- properties .append (
836- exp .DuplicateKeyProperty (expressions = [exp .to_column (duplicate_key .this )])
837- )
838- elif isinstance (duplicate_key , str ):
839- properties .append (
840- exp .DuplicateKeyProperty (expressions = [exp .to_column (duplicate_key )])
841- )
842- # Note: Materialized views don't typically use duplicate_key, so we skip it
866+ column_names .append (str (expr ))
867+ properties .append (
868+ exp .DuplicateKeyProperty (expressions = [exp .to_column (k ) for k in column_names ])
869+ )
870+ elif isinstance (duplicate_key , exp .Column ):
871+ # Handle as single column
872+ if hasattr (duplicate_key , "this" ) and hasattr (duplicate_key .this , "this" ):
873+ column_name = str (duplicate_key .this .this )
874+ else :
875+ column_name = str (duplicate_key .this )
876+ properties .append (
877+ exp .DuplicateKeyProperty (expressions = [exp .to_column (column_name )])
878+ )
879+ elif isinstance (duplicate_key , exp .Literal ):
880+ properties .append (
881+ exp .DuplicateKeyProperty (expressions = [exp .to_column (duplicate_key .this )])
882+ )
883+ elif isinstance (duplicate_key , str ):
884+ properties .append (
885+ exp .DuplicateKeyProperty (expressions = [exp .to_column (duplicate_key )])
886+ )
843887
844888 if table_description :
845889 properties .append (
@@ -851,30 +895,8 @@ def _parse_trigger_string(
851895 # Handle partitioning
852896 add_partition = True
853897 if partitioned_by :
854- normalized_partitioned_by : t .List [exp .Expression ] = []
855- for expr in partitioned_by :
856- try :
857- # Handle literal strings like "RANGE(col)" or "LIST(col)"
858- if isinstance (expr , exp .Literal ) and getattr (expr , "is_string" , False ):
859- text = str (expr .this )
860- match = re .match (
861- r"^\s*(RANGE|LIST)\s*\((.*?)\)\s*$" , text , flags = re .IGNORECASE
862- )
863- if match :
864- inner = match .group (2 )
865- inner_cols = [
866- c .strip ().strip ("`" ) for c in inner .split ("," ) if c .strip ()
867- ]
868- for col in inner_cols :
869- normalized_partitioned_by .append (exp .to_column (col ))
870- continue
871- except Exception :
872- # If anything goes wrong, keep the original expr
873- pass
874- normalized_partitioned_by .append (expr )
875-
876- # Replace with normalized expressions
877- partitioned_by = normalized_partitioned_by
898+ # Parse and normalize partition expressions
899+ partitioned_by , _ = self ._parse_partition_expressions (partitioned_by )
878900 # For tables, check if partitioned_by columns are in unique_key; for materialized views, allow regardless
879901 if unique_key is not None and not is_materialized_view :
880902 # Extract key column names from unique_key (only Tuple or Column expressions)
0 commit comments