@@ -6472,46 +6472,57 @@ def delete_record_recursive(
6472
6472
return None
6473
6473
6474
6474
def duplicate_record (self , dataset : DataSet , children : bool ) -> pd .DataFrame :
6475
- # https://stackoverflow.com/questions/1716320/how-to-insert-duplicate-rows-in-sqlite-with-a-unique-id # fmt: skip # noqa E501
6476
- # This can be done using * syntax without knowing the schema of the table
6477
- # (other than primary key column). The trick is to create a temporary table
6478
- # using the "CREATE TABLE AS" syntax.
6479
- description = (
6480
- f"{ lang .duplicate_prepend } "
6481
- f"{ dataset .get_description_for_pk (dataset .get_current_pk ())} "
6482
- )
6475
+ """
6476
+ Duplicates a record in a database table and optionally duplicates its dependent
6477
+ records.
6478
+
6479
+ The function uses all columns found in `Dataset.column_info` and
6480
+ select all except the primary key column, inserting a duplicate record with the
6481
+ same column values.
6482
+
6483
+ If the `children` parameter is set to `True`, the function duplicates the
6484
+ dependent records by setting the foreign key column of the child records to the
6485
+ primary key value of the newly duplicated record before inserting them.
6486
+
6487
+ Note that this function assumes the primary key column is auto-incrementing and
6488
+ that no columns are set to unique.
6489
+
6490
+ :param dataset: The `Dataset` of the the record to be duplicated.
6491
+ :param children: (optional) Whether to duplicate dependent records. Defaults to
6492
+ False.
6493
+ """
6494
+
6495
+ # Get variables
6483
6496
table = self .quote_table (dataset .table )
6484
- pk_column = self .quote_column (dataset .pk_column )
6485
- current_pk = dataset .get_current (dataset .pk_column )
6486
- description_column = self .quote_column (dataset .description_column )
6487
6497
columns = [
6488
6498
self .quote_column (column ["name" ])
6489
6499
for column in dataset .column_info
6490
6500
if column ["name" ] != dataset .pk_column
6491
6501
]
6492
6502
columns = ", " .join (columns )
6493
- pk = None # we will update this later...
6503
+ pk_column = self .quote_column (dataset .pk_column )
6504
+ current_pk = dataset .get_current (dataset .pk_column )
6494
6505
6495
- # Insert new row
6496
- query = (
6497
- f"INSERT INTO { table } ({ columns } ) "
6498
- f"SELECT { columns } FROM { table } "
6499
- f"WHERE { pk_column } = { current_pk } ;"
6500
- )
6506
+ # Insert new record
6507
+ query = self .get_duplicate_parent_query (table , columns , pk_column , current_pk )
6501
6508
res = self .execute (query )
6502
6509
if res .attrs ["exception" ]:
6503
6510
return res
6504
- if pk is None and res .attrs ["lastrowid" ] is not None :
6505
- # Now we save the new pk
6506
- pk = res .attrs ["lastrowid" ]
6507
6511
6508
- # Update the description
6512
+ # Get pk of new record
6513
+ new_pk = self .get_duplicate_parent_new_pk (res , dataset .pk_column )
6514
+
6515
+ # Set description
6516
+ description_column = self .quote_column (dataset .description_column )
6517
+ description = (
6518
+ f"{ lang .duplicate_prepend } "
6519
+ f"{ dataset .get_description_for_pk (dataset .get_current_pk ())} "
6520
+ )
6509
6521
query = (
6510
6522
f"UPDATE { table } "
6511
- f"SET { description_column } = ? "
6512
- f"WHERE { pk_column } = { pk } ;"
6523
+ f"SET { description_column } = { self . placeholder } "
6524
+ f"WHERE { pk_column } = { new_pk } ;"
6513
6525
)
6514
-
6515
6526
res = self .execute (query , [description ])
6516
6527
if res .attrs ["exception" ]:
6517
6528
return res
@@ -6528,26 +6539,26 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame:
6528
6539
and (r .child_table not in child_duplicated )
6529
6540
):
6530
6541
child = self .quote_table (r .child_table )
6531
- pk_column = self .quote_column (
6532
- dataset .frm [r .child_table ].pk_column
6533
- )
6534
6542
fk_column = self .quote_column (r .fk_column )
6543
+
6544
+ # all columns except fk_column
6535
6545
columns = [
6536
6546
self .quote_column (column ["name" ])
6537
6547
for column in dataset .frm [r .child_table ].column_info
6538
6548
if column ["name" ] != dataset .frm [r .child_table ].pk_column
6539
6549
]
6540
-
6541
- # use new pk on insert
6550
+
6551
+ # replace fk_column with pk of new parent
6542
6552
select_columns = [
6543
- str (pk )
6553
+ str (new_pk )
6544
6554
if column == self .quote_column (r .fk_column )
6545
6555
else column
6546
6556
for column in columns
6547
6557
]
6558
+
6559
+ # prepare query & execute
6548
6560
columns = ", " .join (columns )
6549
6561
select_columns = ", " .join (select_columns )
6550
-
6551
6562
query = (
6552
6563
f"INSERT INTO { child } ({ columns } ) "
6553
6564
f"SELECT { select_columns } FROM { child } "
@@ -6558,9 +6569,20 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame:
6558
6569
return res
6559
6570
6560
6571
child_duplicated .append (r .child_table )
6561
- # If we made it here, we can return the pk. Since the pk was stored earlier,
6562
- # we will just send and empty dataframe. TODO: will this work as expeted still?
6563
- return Result .set (lastrowid = pk )
6572
+ # If we made it here, we can return the pk.
6573
+ # Since the pk was stored earlier, we will just send an empty dataframe.
6574
+ return Result .set (lastrowid = new_pk )
6575
+
6576
+ def get_duplicate_parent_query (self , table , columns , pk_column , current_pk ):
6577
+ return (
6578
+ f"INSERT INTO { table } ({ columns } ) "
6579
+ f"SELECT { columns } FROM { table } "
6580
+ f"WHERE { pk_column } = { current_pk } "
6581
+ f"RETURNING { pk_column } ;"
6582
+ )
6583
+
6584
+ def get_duplicate_parent_new_pk (self , res , pk_column ):
6585
+ return res .iloc [0 ][pk_column ].tolist ()
6564
6586
6565
6587
def save_record (
6566
6588
self , dataset : DataSet , changed_row : dict , where_clause : str = None
@@ -7646,6 +7668,14 @@ def pk_column(self, table):
7646
7668
return rows .iloc [0 ]["COLUMN_NAME" ]
7647
7669
return None
7648
7670
7671
+ def get_duplicate_parent_query (self , table , columns , pk_column , current_pk ):
7672
+ return (
7673
+ f"INSERT INTO { table } ({ columns } ) "
7674
+ f"OUTPUT inserted.{ pk_column } "
7675
+ f"SELECT { columns } FROM { table } "
7676
+ f"WHERE { pk_column } = { current_pk } ;"
7677
+ )
7678
+
7649
7679
7650
7680
# --------------------------------------------------------------------------------------
7651
7681
# MS ACCESS DRIVER
@@ -7889,96 +7919,16 @@ def _get_column_definitions(self, table_name):
7889
7919
7890
7920
return cols
7891
7921
7892
- def duplicate_record (self , dataset : DataSet , children : bool ) -> pd .DataFrame :
7893
- # https://stackoverflow.com/questions/1716320/how-to-insert-duplicate-rows-in-sqlite-with-a-unique-id # fmt: skip # noqa: E501
7894
- # This can be done using * syntax without knowing the schema of the table
7895
- # (other than primary key column). The trick is to create a temporary table
7896
- # using the "CREATE TABLE AS" syntax.
7897
-
7898
- description = self .quote_value (
7899
- f"{ lang .duplicate_prepend } "
7900
- f"{ dataset .get_description_for_pk (dataset .get_current_pk ())} "
7922
+ def get_duplicate_parent_query (self , table , columns , pk_column , current_pk ):
7923
+ return (
7924
+ f"INSERT INTO { table } ({ columns } ) "
7925
+ f"SELECT { columns } FROM { table } "
7926
+ f"WHERE { pk_column } = { current_pk } ;"
7901
7927
)
7902
- table = dataset .table
7903
- tmp_table = f"temp_{ dataset .table } "
7904
- pk_column = dataset .pk_column
7905
- description_column = dataset .description_column
7906
-
7907
- # Create tmp table, update pk column in temp and insert into table
7908
- query = []
7909
- if tmp_table in self .get_tables ():
7910
- query .append (f"DROP TABLE [{ tmp_table } ];" )
7911
-
7912
- query += [
7913
- f"CREATE TABLE [{ tmp_table } ] ({ self ._get_column_definitions (table )} );" ,
7914
- (
7915
- f"INSERT INTO [{ tmp_table } ] (SELECT * FROM [{ table } ] "
7916
- f"WHERE { pk_column } ={ dataset .get_current (dataset .pk_column )} );"
7917
- ),
7918
- (
7919
- f"UPDATE [{ tmp_table } ] SET { pk_column } = "
7920
- f"{ self .next_pk (dataset .table , dataset .pk_column )} ;"
7921
- ),
7922
- f"UPDATE [{ tmp_table } ] SET { description_column } = { description } " ,
7923
- f"INSERT INTO [{ table } ] SELECT * FROM [{ tmp_table } ];" ,
7924
- f"DROP TABLE [{ tmp_table } ]" ,
7925
- ]
7926
- for q in query :
7927
- res = self .execute (q , auto_commit_rollback = True )
7928
- if res .attrs ["exception" ]:
7929
- return res
7930
7928
7931
- # Now we save the new pk
7929
+ def get_duplicate_parent_new_pk ( self , res , pk_column ):
7932
7930
res = self .execute ("SELECT @@IDENTITY AS ID" )
7933
- lastrowid = res .loc [0 ]["ID" ]
7934
-
7935
- # create list of which children we have duplicated
7936
- child_duplicated = []
7937
- # Next, duplicate the child records!
7938
- if children :
7939
- for _ in dataset .frm .datasets :
7940
- for r in dataset .frm .relationships :
7941
- if (
7942
- r .parent_table == dataset .table
7943
- and r .on_update_cascade
7944
- and (r .child_table not in child_duplicated )
7945
- ):
7946
- child = r .child_table
7947
- tmp_child = f"temp_{ r .child_table } "
7948
- pk_column = dataset .frm [r .child_table ].pk_column
7949
- fk_column = r .fk_column
7950
-
7951
- # Update children's pk_columns to NULL and set correct parent
7952
- # PK value.
7953
- query = []
7954
- if tmp_child in self .get_tables ():
7955
- query .append (f"DROP TABLE [{ tmp_child } ];" )
7956
-
7957
- query += [
7958
- (
7959
- f"CREATE TABLE [{ tmp_table } ] "
7960
- f"({ self ._get_column_definitions (table )} );"
7961
- ),
7962
- (
7963
- f"INSERT INTO [{ tmp_table } ] (SELECT * FROM [{ table } ] "
7964
- f"WHERE { pk_column } ="
7965
- f"{ dataset .get_current (dataset .pk_column )} );"
7966
- ),
7967
- # don't next_pk(), because child can be plural.
7968
- f"UPDATE [{ tmp_child } ] SET { pk_column } = NULL;" ,
7969
- f"UPDATE [{ tmp_child } ] SET { fk_column } = { lastrowid } " ,
7970
- f"INSERT INTO [{ child } ] SELECT * FROM [{ tmp_child } ];" ,
7971
- f"DROP TABLE [{ tmp_child } ]" ,
7972
- ]
7973
- for q in query :
7974
- res = self .execute (q )
7975
- if res .attrs ["exception" ]:
7976
- return res
7977
-
7978
- child_duplicated .append (r .child_table )
7979
- # If we made it here, we can return the pk. Since the pk was stored earlier,
7980
- # we will just send and empty DataFrame
7981
- return Result .set (lastrowid = lastrowid )
7931
+ return res .iloc [0 ]["ID" ].tolist ()
7982
7932
7983
7933
def insert_record (self , table : str , pk : int , pk_column : str , row : dict ):
7984
7934
# Remove the pk column
0 commit comments