@@ -79,7 +79,6 @@ def _create_non_partitioned_table(
7979
8080 def _create_partitioned_table (self , * , base_table : str , copy_table : str ) -> None :
8181 assert self .partition_config is not None
82- assert isinstance (self .partition_config .strategy , _partition .DateRangeStrategy )
8382
8483 # Create the parent partitioned table
8584 self .cur .execute (
@@ -105,6 +104,23 @@ def _create_partitioned_table(self, *, base_table: str, copy_table: str) -> None
105104 def _create_partitions (self , * , base_table : str , copy_table : str ) -> None :
106105 assert self .partition_config is not None
107106 strategy = self .partition_config .strategy
107+
108+ if isinstance (strategy , _partition .DateRangeStrategy ):
109+ self ._create_date_range_partitions (
110+ base_table = base_table , copy_table = copy_table
111+ )
112+ elif isinstance (strategy , _partition .NumericRangeStrategy ):
113+ self ._create_numeric_range_partitions (
114+ base_table = base_table , copy_table = copy_table
115+ )
116+ else : # pragma: no cover
117+ raise NotImplementedError
118+
119+ def _create_date_range_partitions (
120+ self , * , base_table : str , copy_table : str
121+ ) -> None :
122+ assert self .partition_config is not None
123+ strategy = self .partition_config .strategy
108124 assert isinstance (strategy , _partition .DateRangeStrategy )
109125
110126 num_of_extra_partitions = self .partition_config .num_of_extra_partitions_ahead
@@ -260,6 +276,121 @@ def _create_datetime_partition(
260276 .as_string (self .conn )
261277 )
262278
279+ def _create_numeric_range_partitions (
280+ self , * , base_table : str , copy_table : str
281+ ) -> None :
282+ assert self .partition_config is not None
283+ strategy = self .partition_config .strategy
284+ assert isinstance (strategy , _partition .NumericRangeStrategy )
285+
286+ # Get the PK column name - this should be the same as partition_config.column
287+ pk_column = self .partition_config .column
288+ num_of_extra_partitions = self .partition_config .num_of_extra_partitions_ahead
289+
290+ # Get min and max positive values
291+ min_and_max_positive = self .introspector .get_min_and_max_pk (
292+ table = base_table , pk_column = pk_column , positive = True
293+ )
294+ # Get min and max negative values
295+ min_and_max_negative = self .introspector .get_min_and_max_pk (
296+ table = base_table , pk_column = pk_column , positive = False
297+ )
298+
299+ # Handle negative values if they exist
300+ if min_and_max_negative :
301+ min_negative , max_negative = min_and_max_negative
302+ # Align min_negative to range boundary (floor division)
303+ partition_start = (
304+ min_negative // strategy .range_size
305+ ) * strategy .range_size
306+ # Align max_negative to next boundary
307+ partition_end = (
308+ (max_negative // strategy .range_size ) + 1
309+ ) * strategy .range_size
310+
311+ current_start = partition_start
312+ while current_start < partition_end :
313+ current_end = current_start + strategy .range_size
314+ partition_suffix = self ._get_numeric_partition_suffix (
315+ start = current_start , end = current_end
316+ )
317+ self ._create_numeric_partition (
318+ base_table = base_table ,
319+ copy_table = copy_table ,
320+ partition_suffix = partition_suffix ,
321+ start = current_start ,
322+ end = current_end ,
323+ )
324+ current_start = current_end
325+
326+ # Handle positive values if they exist
327+ if min_and_max_positive :
328+ min_positive , max_positive = min_and_max_positive
329+ # Align min_positive to range boundary (floor division)
330+ partition_start = (
331+ min_positive // strategy .range_size
332+ ) * strategy .range_size
333+ # Calculate end including extra partitions
334+ partition_end = (
335+ (max_positive // strategy .range_size ) + 1 + num_of_extra_partitions
336+ ) * strategy .range_size
337+
338+ current_start = partition_start
339+ while current_start < partition_end :
340+ current_end = current_start + strategy .range_size
341+ partition_suffix = self ._get_numeric_partition_suffix (
342+ start = current_start , end = current_end
343+ )
344+ self ._create_numeric_partition (
345+ base_table = base_table ,
346+ copy_table = copy_table ,
347+ partition_suffix = partition_suffix ,
348+ start = current_start ,
349+ end = current_end ,
350+ )
351+ current_start = current_end
352+
353+ def _get_numeric_partition_suffix (self , * , start : int , end : int ) -> str :
354+ """
355+ Generate a numeric range partition suffix.
356+ For negative ranges: returns pn1000_n1 (for -1000 to -1)
357+ For positive ranges: returns p0_999 (for 0 to 999)
358+ """
359+ if start < 0 :
360+ return f"pn{ abs (start )} _n{ abs (end )} "
361+ else :
362+ return f"p{ start } _{ end - 1 } "
363+
364+ def _create_numeric_partition (
365+ self ,
366+ * ,
367+ base_table : str ,
368+ copy_table : str ,
369+ partition_suffix : str ,
370+ start : int ,
371+ end : int ,
372+ ) -> None :
373+ """Create a single numeric range partition."""
374+ self .cur .execute (
375+ psycopg .sql .SQL (
376+ dedent ("""
377+ CREATE TABLE {schema}.{partition_name}
378+ PARTITION OF {schema}.{copy_table}
379+ FOR VALUES FROM ({start}) TO ({end});
380+ """ )
381+ )
382+ .format (
383+ schema = psycopg .sql .Identifier (self .schema ),
384+ partition_name = psycopg .sql .Identifier (
385+ f"{ base_table } _{ partition_suffix } "
386+ ),
387+ copy_table = psycopg .sql .Identifier (copy_table ),
388+ start = psycopg .sql .Literal (start ),
389+ end = psycopg .sql .Literal (end ),
390+ )
391+ .as_string (self .conn )
392+ )
393+
263394 def drop_sequence_if_exists (self , * , seq : str ) -> None :
264395 self .cur .execute (
265396 psycopg .sql .SQL ("DROP SEQUENCE IF EXISTS {schema}.{seq};" )
@@ -307,11 +438,14 @@ def set_table_id_seq(self, *, table: str, seq: str, pk_column: str) -> None:
307438 def add_pk (self , * , table : str , pk_column : str ) -> None :
308439 # For partitioned tables, the PK must include all partitioning columns
309440 if self .partition_config :
441+ # Build list of unique columns for the primary key
442+ pk_column_list = [pk_column ]
443+ # Only add partition column if it's different from pk_column
444+ if self .partition_config .column != pk_column :
445+ pk_column_list .append (self .partition_config .column )
446+
310447 pk_columns = psycopg .sql .SQL (", " ).join (
311- [
312- psycopg .sql .Identifier (pk_column ),
313- psycopg .sql .Identifier (self .partition_config .column ),
314- ]
448+ [psycopg .sql .Identifier (col ) for col in pk_column_list ]
315449 )
316450 self .cur .execute (
317451 psycopg .sql .SQL (
0 commit comments