Skip to content

Commit

Permalink
Allow hypertable constraint creation USING INDEX
Browse files Browse the repository at this point in the history
Before this commit, the following operation was disallowed:

```sql
ALTER TABLE foo ADD CONSTRAINT foo_pkey
PRIMARY KEY USING INDEX foo_pkey
```

pgcopydb, the tool that powers our migration process[1], handles primary key
and unique constraint migrations in two steps:

1. Build unique indexes.
2. Create the unique constraint or primary key using the unique index created
   in step 1.

This multi-step approach is useful for reducing the time spent on
EXCLUSIVE LOCKS while building unique constraints or primary keys.

[1] https://docs.timescale.com/migrate/latest/live-migration/

Signed-off-by: Arunprasad Rajkumar <[email protected]>
  • Loading branch information
arajkumar committed Aug 27, 2024
1 parent bedc86e commit 1546932
Show file tree
Hide file tree
Showing 16 changed files with 359 additions and 84 deletions.
1 change: 1 addition & 0 deletions .unreleased/fix_7040
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixes: #7040 Allow hypertable constraint creation USING INDEX
45 changes: 36 additions & 9 deletions sql/chunk_constraint.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

-- create constraint on newly created chunk based on hypertable constraint
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_constraint_add_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint
chunk_constraint_row _timescaledb_catalog.chunk_constraint,
using_index BOOLEAN
)
RETURNS VOID LANGUAGE PLPGSQL AS
$BODY$
Expand All @@ -17,6 +18,8 @@ DECLARE
def TEXT;
indx_tablespace NAME;
tablespace_def TEXT;
chunk_constraint_index_name NAME;
row_record RECORD;
BEGIN
SELECT * INTO STRICT chunk_row FROM _timescaledb_catalog.chunk c WHERE c.id = chunk_constraint_row.chunk_id;
SELECT * INTO STRICT hypertable_row FROM _timescaledb_catalog.hypertable h WHERE h.id = chunk_row.hypertable_id;
Expand All @@ -30,15 +33,39 @@ BEGIN
conrelid = format('%I.%I', hypertable_row.schema_name, hypertable_row.table_name)::regclass::oid;

IF constraint_type IN ('p','u') THEN
-- since primary keys and unique constraints are backed by an index
-- they might have an index tablespace assigned
-- the tablspace is not part of the constraint definition so
-- we have to append it explicitly to preserve it
SELECT T.spcname INTO indx_tablespace
FROM pg_constraint C, pg_class I, pg_tablespace T
WHERE C.oid = constraint_oid AND C.contype IN ('p', 'u') AND I.oid = C.conindid AND I.reltablespace = T.oid;
IF using_index THEN
-- indexes created for constraints are named after the constraint
-- so we can find the index name by looking up the constraint name.
SELECT index_name INTO STRICT chunk_constraint_index_name FROM _timescaledb_catalog.chunk_index
WHERE chunk_id = chunk_row.id AND hypertable_index_name = chunk_constraint_row.hypertable_constraint_name;

def := pg_get_constraintdef(constraint_oid);
IF chunk_constraint_index_name IS NULL THEN
RAISE 'index not found for constraint %', chunk_constraint_row;
END IF;

CASE constraint_type
WHEN 'p' THEN
def := pg_catalog.format(
$$ PRIMARY KEY USING INDEX %I $$,
chunk_constraint_index_name
);
WHEN 'u' THEN
def := pg_catalog.format(
$$ UNIQUE USING INDEX %I $$,
chunk_constraint_index_name
);
END CASE;
ELSE
-- since primary keys and unique constraints are backed by an index
-- they might have an index tablespace assigned
-- the tablspace is not part of the constraint definition so
-- we have to append it explicitly to preserve it
SELECT T.spcname INTO indx_tablespace
FROM pg_constraint C, pg_class I, pg_tablespace T
WHERE C.oid = constraint_oid AND C.contype IN ('p', 'u') AND I.oid = C.conindid AND I.reltablespace = T.oid;

def := pg_get_constraintdef(constraint_oid);
END IF;

ELSIF constraint_type = 't' THEN
-- constraint triggers are copied separately with normal triggers
Expand Down
4 changes: 2 additions & 2 deletions sql/compat.sql
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ END$$
SET search_path TO pg_catalog,pg_temp;


CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_constraint_add_table_constraint(chunk_constraint_row _timescaledb_catalog.chunk_constraint) RETURNS void LANGUAGE PLPGSQL AS $$
CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_constraint_add_table_constraint(chunk_constraint_row _timescaledb_catalog.chunk_constraint,using_index BOOLEAN) RETURNS void LANGUAGE PLPGSQL AS $$
BEGIN
IF current_setting('timescaledb.enable_deprecation_warnings', true)::bool THEN
RAISE WARNING 'function _timescaledb_internal.chunk_constraint_add_table_constraint(_timescaledb_catalog.chunk_constraint) is deprecated and has been moved to _timescaledb_functions schema. this compatibility function will be removed in a future version.';
END IF;
PERFORM _timescaledb_functions.chunk_constraint_add_table_constraint($1);
PERFORM _timescaledb_functions.chunk_constraint_add_table_constraint($1,$2);
END$$
SET search_path TO pg_catalog,pg_temp;

Expand Down
7 changes: 7 additions & 0 deletions sql/updates/latest-dev.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@ CREATE FUNCTION _timescaledb_functions.compressed_data_info(_timescaledb_interna
RETURNS TABLE (algorithm name, has_nulls bool)
AS '@MODULE_PATHNAME@', 'ts_update_placeholder'
LANGUAGE C STRICT IMMUTABLE SET search_path = pg_catalog, pg_temp;

DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_constraint_add_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint
);
DROP FUNCTION IF EXISTS _timescaledb_internal.chunk_constraint_add_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint
);
81 changes: 81 additions & 0 deletions sql/updates/reverse-dev.sql
Original file line number Diff line number Diff line change
@@ -1 +1,82 @@
DROP FUNCTION _timescaledb_functions.compressed_data_info(_timescaledb_internal.compressed_data);

DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_constraint_add_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint,
using_index BOOLEAN
);
DROP FUNCTION IF EXISTS _timescaledb_internal.chunk_constraint_add_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint,
using_index BOOLEAN
);

CREATE FUNCTION _timescaledb_functions.chunk_constraint_add_table_constraint(
chunk_constraint_row _timescaledb_catalog.chunk_constraint
)
RETURNS VOID LANGUAGE PLPGSQL AS
$BODY$
DECLARE
chunk_row _timescaledb_catalog.chunk;
hypertable_row _timescaledb_catalog.hypertable;
constraint_oid OID;
constraint_type CHAR;
check_sql TEXT;
def TEXT;
indx_tablespace NAME;
tablespace_def TEXT;
BEGIN
SELECT * INTO STRICT chunk_row FROM _timescaledb_catalog.chunk c WHERE c.id = chunk_constraint_row.chunk_id;
SELECT * INTO STRICT hypertable_row FROM _timescaledb_catalog.hypertable h WHERE h.id = chunk_row.hypertable_id;

IF chunk_constraint_row.dimension_slice_id IS NOT NULL THEN
RAISE 'cannot create dimension constraint %', chunk_constraint_row;
ELSIF chunk_constraint_row.hypertable_constraint_name IS NOT NULL THEN

SELECT oid, contype INTO STRICT constraint_oid, constraint_type FROM pg_constraint
WHERE conname=chunk_constraint_row.hypertable_constraint_name AND
conrelid = format('%I.%I', hypertable_row.schema_name, hypertable_row.table_name)::regclass::oid;

IF constraint_type IN ('p','u') THEN
-- since primary keys and unique constraints are backed by an index
-- they might have an index tablespace assigned
-- the tablspace is not part of the constraint definition so
-- we have to append it explicitly to preserve it
SELECT T.spcname INTO indx_tablespace
FROM pg_constraint C, pg_class I, pg_tablespace T
WHERE C.oid = constraint_oid AND C.contype IN ('p', 'u') AND I.oid = C.conindid AND I.reltablespace = T.oid;

def := pg_get_constraintdef(constraint_oid);

ELSIF constraint_type = 't' THEN
-- constraint triggers are copied separately with normal triggers
def := NULL;
ELSE
def := pg_get_constraintdef(constraint_oid);
END IF;

ELSE
RAISE 'unknown constraint type';
END IF;

IF def IS NOT NULL THEN
-- to allow for custom types with operators outside of pg_catalog
-- we set search_path to @extschema@
SET LOCAL search_path TO @extschema@, pg_temp;
EXECUTE pg_catalog.format(
$$ ALTER TABLE %I.%I ADD CONSTRAINT %I %s $$,
chunk_row.schema_name, chunk_row.table_name, chunk_constraint_row.constraint_name, def
);

-- if constraint (primary or unique) needs a tablespace then add it
-- via a separate ALTER INDEX SET TABLESPACE command. We cannot append it
-- to the "def" string above since it leads to a SYNTAX error when
-- "DEFERRABLE" or "INITIALLY DEFERRED" are used in the constraint
IF indx_tablespace IS NOT NULL THEN
EXECUTE pg_catalog.format(
$$ ALTER INDEX %I.%I SET TABLESPACE %I $$,
chunk_row.schema_name, chunk_constraint_row.constraint_name, indx_tablespace
);
END IF;

END IF;
END
$BODY$ SET search_path TO pg_catalog, pg_temp;
21 changes: 13 additions & 8 deletions src/chunk_constraint.c
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ create_dimension_check_constraint(const Dimension *dim, const DimensionSlice *sl
* Add a constraint to a chunk table.
*/
static Oid
chunk_constraint_create_on_table(const ChunkConstraint *cc, Oid chunk_oid)
chunk_constraint_create_on_table(const ChunkConstraint *cc, Oid chunk_oid, bool using_index)
{
HeapTuple tuple;
Datum values[Natts_chunk_constraint];
Expand All @@ -389,7 +389,9 @@ chunk_constraint_create_on_table(const ChunkConstraint *cc, Oid chunk_oid)
RelationClose(rel);

ts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);
CatalogInternalCall1(DDL_ADD_CHUNK_CONSTRAINT, HeapTupleGetDatum(tuple));
CatalogInternalCall2(DDL_ADD_CHUNK_CONSTRAINT,
HeapTupleGetDatum(tuple),
BoolGetDatum(using_index));
ts_catalog_restore_user(&sec_ctx);
heap_freetuple(tuple);

Expand All @@ -402,14 +404,14 @@ chunk_constraint_create_on_table(const ChunkConstraint *cc, Oid chunk_oid)
*/
static Oid
create_non_dimensional_constraint(const ChunkConstraint *cc, Oid chunk_oid, int32 chunk_id,
Oid hypertable_oid, int32 hypertable_id)
Oid hypertable_oid, int32 hypertable_id, bool using_index)
{
Oid chunk_constraint_oid;

Assert(!is_dimension_constraint(cc));

ts_process_utility_set_expect_chunk_modification(true);
chunk_constraint_oid = chunk_constraint_create_on_table(cc, chunk_oid);
chunk_constraint_oid = chunk_constraint_create_on_table(cc, chunk_oid, using_index);
ts_process_utility_set_expect_chunk_modification(false);

/*
Expand All @@ -431,7 +433,7 @@ create_non_dimensional_constraint(const ChunkConstraint *cc, Oid chunk_oid, int3
{
FormData_pg_constraint *constr = (FormData_pg_constraint *) GETSTRUCT(tuple);

if (OidIsValid(constr->conindid) && constr->contype != CONSTRAINT_FOREIGN)
if (OidIsValid(constr->conindid) && constr->contype != CONSTRAINT_FOREIGN && !using_index)
ts_chunk_index_create_from_constraint(hypertable_id,
hypertable_constraint_oid,
chunk_id,
Expand Down Expand Up @@ -494,7 +496,8 @@ ts_chunk_constraints_create(const Hypertable *ht, const Chunk *chunk)
chunk->table_id,
chunk->fd.id,
ht->main_table_relid,
ht->fd.id);
ht->fd.id,
false);
}
}

Expand Down Expand Up @@ -838,7 +841,8 @@ ts_chunk_constraints_add_inheritable_check_constraints(ChunkConstraints *ccs, in
}

void
ts_chunk_constraint_create_on_chunk(const Hypertable *ht, const Chunk *chunk, Oid constraint_oid)
ts_chunk_constraint_create_on_chunk(const Hypertable *ht, const Chunk *chunk, Oid constraint_oid,
bool using_index)
{
HeapTuple tuple;
Form_pg_constraint con;
Expand All @@ -863,7 +867,8 @@ ts_chunk_constraint_create_on_chunk(const Hypertable *ht, const Chunk *chunk, Oi
chunk->table_id,
chunk->fd.id,
ht->main_table_relid,
ht->fd.id);
ht->fd.id,
using_index);
}

ReleaseSysCache(tuple);
Expand Down
2 changes: 1 addition & 1 deletion src/chunk_constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ extern TSDLLEXPORT int ts_chunk_constraints_add_inheritable_check_constraints(
extern TSDLLEXPORT void ts_chunk_constraints_insert_metadata(const ChunkConstraints *ccs);
extern TSDLLEXPORT void ts_chunk_constraints_create(const Hypertable *ht, const Chunk *chunk);
extern void ts_chunk_constraint_create_on_chunk(const Hypertable *ht, const Chunk *chunk,
Oid constraint_oid);
Oid constraint_oid, bool using_index);
extern int ts_chunk_constraint_delete_by_hypertable_constraint_name(
int32 chunk_id, const char *hypertable_constraint_name, bool delete_metadata,
bool drop_constraint);
Expand Down
14 changes: 6 additions & 8 deletions src/chunk_index.c
Original file line number Diff line number Diff line change
Expand Up @@ -989,12 +989,11 @@ ts_chunk_index_adjust_meta(int32 chunk_id, const char *ht_index_name, const char
}

int
ts_chunk_index_rename(Chunk *chunk, Oid chunk_indexrelid, const char *new_name)
ts_chunk_index_rename(Chunk *chunk, const char *old_name, const char *new_name)
{
ScanKeyData scankey[2];
const char *indexname = get_rel_name(chunk_indexrelid);
ChunkIndexRenameInfo renameinfo = {
.oldname = indexname,
.oldname = old_name,
.newname = new_name,
};

Expand All @@ -1007,7 +1006,7 @@ ts_chunk_index_rename(Chunk *chunk, Oid chunk_indexrelid, const char *new_name)
Anum_chunk_index_chunk_id_index_name_idx_index_name,
BTEqualStrategyNumber,
F_NAMEEQ,
CStringGetDatum(indexname));
CStringGetDatum(old_name));

return chunk_index_scan_update(CHUNK_INDEX_CHUNK_ID_INDEX_NAME_IDX,
scankey,
Expand All @@ -1018,12 +1017,11 @@ ts_chunk_index_rename(Chunk *chunk, Oid chunk_indexrelid, const char *new_name)
}

int
ts_chunk_index_rename_parent(Hypertable *ht, Oid hypertable_indexrelid, const char *new_name)
ts_chunk_index_rename_parent(Hypertable *ht, const char *old_name, const char *new_name)
{
ScanKeyData scankey[2];
const char *indexname = get_rel_name(hypertable_indexrelid);
ChunkIndexRenameInfo renameinfo = {
.oldname = indexname,
.oldname = old_name,
.newname = new_name,
.isparent = true,
};
Expand All @@ -1037,7 +1035,7 @@ ts_chunk_index_rename_parent(Hypertable *ht, Oid hypertable_indexrelid, const ch
Anum_chunk_index_hypertable_id_hypertable_index_name_idx_hypertable_index_name,
BTEqualStrategyNumber,
F_NAMEEQ,
CStringGetDatum(indexname));
CStringGetDatum(old_name));

return chunk_index_scan_update(CHUNK_INDEX_HYPERTABLE_ID_HYPERTABLE_INDEX_NAME_IDX,
scankey,
Expand Down
5 changes: 2 additions & 3 deletions src/chunk_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ extern int ts_chunk_index_delete(int32 chunk_id, const char *indexname, bool dro
extern int ts_chunk_index_delete_by_chunk_id(int32 chunk_id, bool drop_index);
extern void ts_chunk_index_delete_by_name(const char *schema, const char *index_name,
bool drop_index);
extern int ts_chunk_index_rename(Chunk *chunk, Oid chunk_indexrelid, const char *new_name);
extern int ts_chunk_index_rename_parent(Hypertable *ht, Oid hypertable_indexrelid,
const char *new_name);
extern int ts_chunk_index_rename(Chunk *chunk, const char *old_name, const char *new_name);
extern int ts_chunk_index_rename_parent(Hypertable *ht, const char *old_name, const char *new_name);
extern int ts_chunk_index_adjust_meta(int32 chunk_id, const char *ht_index_name,
const char *old_name, const char *new_name);
extern int ts_chunk_index_set_tablespace(Hypertable *ht, Oid hypertable_indexrelid,
Expand Down
Loading

0 comments on commit 1546932

Please sign in to comment.