Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,29 @@ def get_sql_from_diff(self, **kwargs):
target_data['orig_name'] = target_data['name']
target_data['name'] = 'temp_partitioned_{0}'.format(
secrets.choice(range(1, 9999999)))

# If the source table already has a default partition of its own,
# it will be (re)created below via source_data['partitions'], so we
# must not scaffold an extra one (Postgres allows only a single
# default partition per parent). We only need the scaffolding
# default partition - dropped again once the data copy is done -
# when the source has no default partition, to prevent the
# row-copy INSERT from failing on rows that don't match any of the
# real partitions.
source_has_default_partition = any(
partition.get('is_default') for partition in
source_data.get('partitions', []))
target_data['create_scaffolding_default_partition'] = \
not source_has_default_partition

# For PG/EPAS 11 and above when we copy the data from original
# table to temporary table for schema diff, we will have to create
# a default partition to prevent the data loss.
# a default partition to prevent the data loss. Derive its name
# from the already-randomised temporary table name (rather than
# the original table's name) so it cannot collide with an
# existing relation.
target_data['default_partition_name'] = \
target_data['orig_name'] + '_default'
target_data['name'] + '_default'

# Copy the partition scheme from source to target.
if 'partition_scheme' in source_data:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,20 @@ def get_sql_from_submodule_diff(self, **kwargs):
pk_diff = self.table_constraint_comp(source, target)
diff_dict.update(pk_diff)

# When both the source and target tables are partitioned, the
# 'partition' submodule branch below rebuilds the whole table
# (and every one of its partitions) from scratch, handling any
# added, removed or bound-changed partition itself. The generic
# partition add/remove handling in get_sql_from_table_diff (via
# _check_for_partitions_in_sql) must not also run in that case:
# it would detach/recreate the same partitions using their real
# names ahead of the rebuild script, so the rebuild's row-copy
# step (which reads from the original table) would miss rows
# already detached out from under it, silently losing data.
if 'partitions' in diff_dict and \
source.get('is_partitioned') and target.get('is_partitioned'):
del diff_dict['partitions']

# Get the difference DDL/DML statements for table
target_params['diff_data'] = diff_dict
diff = self.get_sql_from_table_diff(**target_params)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
CREATE TABLE IF NOT EXISTS {{conn|qtIdent(data.schema, data.name)}} (
LIKE {{conn|qtIdent(data.schema, data.orig_name)}} INCLUDING ALL
) PARTITION BY {{ data.partition_scheme }};
{{partition_sql}}{{partition_data.default_partition_header}}
{{partition_sql}}{% if data.create_scaffolding_default_partition %}{{partition_data.default_partition_header}}
CREATE TABLE IF NOT EXISTS {{conn|qtIdent(data.schema, data.default_partition_name)}} PARTITION OF {{conn|qtIdent(data.schema, data.name)}} DEFAULT;

{% endif %}
INSERT INTO {{conn|qtIdent(data.schema, data.name)}}(
{% if data.columns and data.columns|length > 0 %}
{% for c in data.columns %} {{c.name}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %})
SELECT {% if data.columns and data.columns|length > 0 %}{% for c in data.columns %}{{c.name}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
FROM {{conn|qtIdent(data.schema, data.orig_name)}};

{% if data.create_scaffolding_default_partition %}
-- The source table has no default partition of its own, so the
-- scaffolding default partition created above (purely to stop the row
-- copy above failing on unmatched rows) is dropped, but only if it is
-- still empty. If any rows were routed into it (i.e. rows that don't
-- fall within the bounds of any other partition), it is left in place
-- so that data is not lost; it becomes the default partition of the
-- rebuilt table.
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM {{conn|qtIdent(data.schema, data.default_partition_name)}}
) THEN
DROP TABLE {{conn|qtIdent(data.schema, data.default_partition_name)}};
END IF;
END;
$$;
{% endif %}
{% if partition_data.partitions and partition_data.partitions|length > 0 %}
{% for part in partition_data.partitions %}
DROP TABLE IF EXISTS {{conn|qtIdent(data.schema, part.partition_name)}};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
CREATE TABLE {{conn|qtIdent(data.schema, data.name)}} (
LIKE {{conn|qtIdent(data.schema, data.orig_name)}} INCLUDING ALL
) PARTITION BY {{ data.partition_scheme }};
{{partition_sql}}{{partition_data.default_partition_header}}
{{partition_sql}}{% if data.create_scaffolding_default_partition %}{{partition_data.default_partition_header}}
CREATE TABLE IF NOT EXISTS {{conn|qtIdent(data.schema, data.default_partition_name)}} PARTITION OF {{conn|qtIdent(data.schema, data.name)}} DEFAULT;

{% endif %}
INSERT INTO {{conn|qtIdent(data.schema, data.name)}}(
{% if data.columns and data.columns|length > 0 %}
{% for c in data.columns %} {{c.name}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %})
SELECT {% if data.columns and data.columns|length > 0 %}{% for c in data.columns %}{{c.name}}{% if not loop.last %},{% endif %}{% endfor %}{% endif %}
FROM {{conn|qtIdent(data.schema, data.orig_name)}};

{% if data.create_scaffolding_default_partition %}
-- The source table has no default partition of its own, so the
-- scaffolding default partition created above (purely to stop the row
-- copy above failing on unmatched rows) is dropped, but only if it is
-- still empty. If any rows were routed into it (i.e. rows that don't
-- fall within the bounds of any other partition), it is left in place
-- so that data is not lost; it becomes the default partition of the
-- rebuilt table.
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM {{conn|qtIdent(data.schema, data.default_partition_name)}}
) THEN
DROP TABLE {{conn|qtIdent(data.schema, data.default_partition_name)}};
END IF;
END;
$$;
{% endif %}
{% if partition_data.partitions and partition_data.partitions|length > 0 %}
{% for part in partition_data.partitions %}
DROP TABLE IF EXISTS {{conn|qtIdent(data.schema, part.partition_name)}};
Expand Down
Loading
Loading