Skip to content

Commit 12cb55b

Browse files
committed
Cleaning up duplicate
I hope this looks good to you. Now completely functional on all sqldrivers, and they select the correct pk afterwords! Since sqlserver and msaccess only needed minor changes, I split out into functions: get_duplicate_parent_query(self, table, columns, pk_column, current_pk) get_duplicate_parent_new_pk(self, res, pk_column)
1 parent ba76efb commit 12cb55b

File tree

1 file changed

+72
-122
lines changed

1 file changed

+72
-122
lines changed

pysimplesql/pysimplesql.py

+72-122
Original file line numberDiff line numberDiff line change
@@ -6472,46 +6472,57 @@ def delete_record_recursive(
64726472
return None
64736473

64746474
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
64836496
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)
64876497
columns = [
64886498
self.quote_column(column["name"])
64896499
for column in dataset.column_info
64906500
if column["name"] != dataset.pk_column
64916501
]
64926502
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)
64946505

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)
65016508
res = self.execute(query)
65026509
if res.attrs["exception"]:
65036510
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"]
65076511

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+
)
65096521
query = (
65106522
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};"
65136525
)
6514-
65156526
res = self.execute(query, [description])
65166527
if res.attrs["exception"]:
65176528
return res
@@ -6528,26 +6539,26 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame:
65286539
and (r.child_table not in child_duplicated)
65296540
):
65306541
child = self.quote_table(r.child_table)
6531-
pk_column = self.quote_column(
6532-
dataset.frm[r.child_table].pk_column
6533-
)
65346542
fk_column = self.quote_column(r.fk_column)
6543+
6544+
# all columns except fk_column
65356545
columns = [
65366546
self.quote_column(column["name"])
65376547
for column in dataset.frm[r.child_table].column_info
65386548
if column["name"] != dataset.frm[r.child_table].pk_column
65396549
]
6540-
6541-
# use new pk on insert
6550+
6551+
# replace fk_column with pk of new parent
65426552
select_columns = [
6543-
str(pk)
6553+
str(new_pk)
65446554
if column == self.quote_column(r.fk_column)
65456555
else column
65466556
for column in columns
65476557
]
6558+
6559+
# prepare query & execute
65486560
columns = ", ".join(columns)
65496561
select_columns = ", ".join(select_columns)
6550-
65516562
query = (
65526563
f"INSERT INTO {child} ({columns}) "
65536564
f"SELECT {select_columns} FROM {child} "
@@ -6558,9 +6569,20 @@ def duplicate_record(self, dataset: DataSet, children: bool) -> pd.DataFrame:
65586569
return res
65596570

65606571
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()
65646586

65656587
def save_record(
65666588
self, dataset: DataSet, changed_row: dict, where_clause: str = None
@@ -7646,6 +7668,14 @@ def pk_column(self, table):
76467668
return rows.iloc[0]["COLUMN_NAME"]
76477669
return None
76487670

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+
76497679

76507680
# --------------------------------------------------------------------------------------
76517681
# MS ACCESS DRIVER
@@ -7889,96 +7919,16 @@ def _get_column_definitions(self, table_name):
78897919

78907920
return cols
78917921

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};"
79017927
)
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
79307928

7931-
# Now we save the new pk
7929+
def get_duplicate_parent_new_pk(self, res, pk_column):
79327930
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()
79827932

79837933
def insert_record(self, table: str, pk: int, pk_column: str, row: dict):
79847934
# Remove the pk column

0 commit comments

Comments
 (0)