Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to rename and change schema on hypertable. #117

Merged
merged 1 commit into from
Jul 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions sql/chunk_index_triggers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ BEGIN
ELSIF TG_OP = 'DELETE' THEN
EXECUTE format('DROP INDEX IF EXISTS %I.%I', OLD.schema_name, OLD.index_name);
RETURN OLD;
ELSIF TG_OP = 'UPDATE' THEN
RETURN NEW;
END IF;

PERFORM _timescaledb_internal.on_trigger_error(TG_OP, TG_TABLE_SCHEMA, TG_TABLE_NAME);
Expand Down
28 changes: 28 additions & 0 deletions sql/ddl_internal.sql
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,31 @@ BEGIN
END IF;
END
$BODY$;


CREATE OR REPLACE FUNCTION _timescaledb_internal.rename_hypertable(
old_schema NAME,
old_table_name NAME,
new_schema TEXT,
new_table_name TEXT
)
RETURNS VOID
LANGUAGE PLPGSQL VOLATILE
SECURITY DEFINER SET search_path = ''
AS
$BODY$
DECLARE
hypertable_row _timescaledb_catalog.hypertable;
BEGIN
SELECT * INTO STRICT hypertable_row
FROM _timescaledb_catalog.hypertable
WHERE schema_name = old_schema AND table_name = old_table_name;

UPDATE _timescaledb_catalog.hypertable SET
schema_name = new_schema,
table_name = new_table_name
WHERE
schema_name = old_schema AND
table_name = old_table_name;
END
$BODY$;
1 change: 0 additions & 1 deletion sql/hypertable_index_triggers.sql
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ DECLARE
hypertable_row _timescaledb_catalog.hypertable;
BEGIN
IF TG_OP = 'UPDATE' THEN
PERFORM _timescaledb_internal.on_trigger_error(TG_OP, TG_TABLE_SCHEMA, TG_TABLE_NAME);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make sure that this is only the supported kind of update? Not sure but a thought

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean checking that all the other columns are not changed ? I considered it but AFAIK it would mean a big if statement comparing individual columns. Also, as this is internal stuff I am not sure we need that level of security but I can add it if you feel strongly about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel too strongly but I think that kind of security gets us closer to production ready.

RETURN NEW;
ELSIF TG_OP = 'INSERT' THEN
-- create index on all chunks
Expand Down
6 changes: 4 additions & 2 deletions sql/tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable (
associated_schema_name NAME NOT NULL,
associated_table_prefix NAME NOT NULL,
num_dimensions SMALLINT NOT NULL CHECK (num_dimensions > 0),
UNIQUE (id, schema_name), -- So hypertable_index can use it as foreign key
UNIQUE (schema_name, table_name),
UNIQUE (associated_schema_name, associated_table_prefix)
);
Expand Down Expand Up @@ -127,10 +128,11 @@ SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_constrain

-- Represents an index on the hypertable
CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable_index (
hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,
hypertable_id INTEGER NOT NULL,
main_schema_name NAME NOT NULL, -- schema name of main table (needed for a uniqueness constraint)
main_index_name NAME NOT NULL, -- index name on main table
definition TEXT NOT NULL, -- def with /*INDEX_NAME*/ and /*TABLE_NAME*/ placeholders
FOREIGN KEY (hypertable_id, main_schema_name) REFERENCES _timescaledb_catalog.hypertable(id, schema_name) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (hypertable_id, main_index_name),
UNIQUE(main_schema_name, main_index_name) -- globally unique since index names globally unique
);
Expand All @@ -147,7 +149,7 @@ CREATE TABLE IF NOT EXISTS _timescaledb_catalog.chunk_index (
definition TEXT NOT NULL,
UNIQUE (schema_name, table_name, index_name),
FOREIGN KEY (schema_name, table_name) REFERENCES _timescaledb_catalog.chunk (schema_name, table_name) ON DELETE CASCADE,
FOREIGN KEY (main_schema_name, main_index_name) REFERENCES _timescaledb_catalog.hypertable_index (main_schema_name, main_index_name) ON DELETE CASCADE
FOREIGN KEY (main_schema_name, main_index_name) REFERENCES _timescaledb_catalog.hypertable_index (main_schema_name, main_index_name) ON DELETE CASCADE ON UPDATE CASCADE
);
SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.chunk_index', '');
SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.chunk_index','id'), '');
54 changes: 54 additions & 0 deletions src/metadata_queries.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
#include <catalog/pg_type.h>
#include <commands/trigger.h>
#include <executor/spi.h>
#include <utils/builtins.h>

#include "metadata_queries.h"
#include "chunk.h"
#include "dimension.h"
#include "hypertable.h"

/* Utility function to prepare an SPI plan */
static SPIPlanPtr
Expand Down Expand Up @@ -47,9 +49,16 @@ prepare_plan(const char *src, int nargs, Oid *argtypes)
#define CHUNK_CREATE_ARGS (Oid[]) {INT4ARRAYOID, INT8ARRAYOID}
#define CHUNK_CREATE "SELECT * FROM _timescaledb_internal.chunk_create($1, $2)"

/* old_schema, old_name, new_schema, new_name */
#define RENAME_HYPERTABLE_ARGS (Oid[]) {NAMEOID, NAMEOID, TEXTOID, TEXTOID}
#define RENAME_HYPERTABLE "SELECT * FROM _timescaledb_internal.rename_hypertable($1, $2, $3, $4)"

/* plan for creating a chunk via create_chunk(). */
DEFINE_PLAN(create_chunk_plan, CHUNK_CREATE, 2, CHUNK_CREATE_ARGS)

/* plan to rename hypertable */
DEFINE_PLAN(rename_hypertable_plan, RENAME_HYPERTABLE, 4, RENAME_HYPERTABLE_ARGS)

static HeapTuple
chunk_tuple_create_spi_connected(Hyperspace *hs, Point *p, SPIPlanPtr plan)
{
Expand Down Expand Up @@ -104,3 +113,48 @@ spi_chunk_create(Hyperspace *hs, Point *p)

return chunk;
}


static void
hypertable_rename_spi_connected(Hypertable *ht, char *new_schema_name, char *new_table_name, SPIPlanPtr plan)
{
int ret;
Datum args[4];

args[0] = PointerGetDatum(NameStr(ht->fd.schema_name));
args[1] = PointerGetDatum(NameStr(ht->fd.table_name));

args[2] = CStringGetTextDatum(new_schema_name);
args[3] = CStringGetTextDatum(new_table_name);

ret = SPI_execute_plan(plan, args, NULL, false, 4);

if (ret <= 0)
elog(ERROR, "Got an SPI error %d", ret);

return;
}

void
spi_hypertable_rename(Hypertable *ht, char *new_schema_name, char *new_table_name)
{
SPIPlanPtr plan = rename_hypertable_plan();


if (strlen(new_schema_name) > sizeof(NameData) - 1) {
elog(ERROR, "New schema name '%s' is too long", new_schema_name);
}

if (strlen(new_table_name) > sizeof(NameData) - 1) {
elog(ERROR, "New schema name '%s' is too long", new_table_name);
}

if (SPI_connect() < 0)
elog(ERROR, "Got an SPI connect error");

hypertable_rename_spi_connected(ht, new_schema_name, new_table_name, plan);

SPI_finish();

return;
}
2 changes: 2 additions & 0 deletions src/metadata_queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
typedef struct Chunk Chunk;
typedef struct Hyperspace Hyperspace;
typedef struct Point Point;
typedef struct Hypertable Hypertable;

extern Chunk *spi_chunk_create(Hyperspace *hs, Point *p);
extern void spi_hypertable_rename(Hypertable *ht, char *new_schema_name, char *new_table_name);

#endif /* TIMESCALEDB_METADATA_QUERIES_H */
42 changes: 36 additions & 6 deletions src/process_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "hypertable_cache.h"
#include "extension.h"
#include "executor.h"
#include "metadata_queries.h"

void _process_utility_init(void);
void _process_utility_fini(void);
Expand Down Expand Up @@ -35,7 +36,8 @@ prev_ProcessUtility(Node *parsetree,
}
}

/* Hook-intercept for ProcessUtility. */
/* Hook-intercept for ProcessUtility. Used to set COPY completion tag and */
/* renaming of hypertables. */
static void
timescaledb_ProcessUtility(Node *parsetree,
const char *query_string,
Expand All @@ -50,7 +52,31 @@ timescaledb_ProcessUtility(Node *parsetree,
return;
}

/* We don't support renaming hypertables yet so we need to block it */
/* Change the schema of hypertable */
if (IsA(parsetree, AlterObjectSchemaStmt))
{
AlterObjectSchemaStmt *alterstmt = (AlterObjectSchemaStmt *) parsetree;
Oid relId = RangeVarGetRelid(alterstmt->relation, NoLock, true);

if (OidIsValid(relId))
{
Cache *hcache = hypertable_cache_pin();
Hypertable *hentry = hypertable_cache_get_entry(hcache, relId);

if (hentry != NULL && alterstmt->objectType == OBJECT_TABLE) {
executor_level_enter();
spi_hypertable_rename(hentry,
alterstmt->newschema,
NameStr(hentry->fd.table_name));
executor_level_exit();
}
cache_release(hcache);
}
prev_ProcessUtility((Node *) alterstmt, query_string, context, params, dest, completion_tag);
return;
}

/* Rename hypertable */
if (IsA(parsetree, RenameStmt))
{
RenameStmt *renamestmt = (RenameStmt *) parsetree;
Expand All @@ -61,15 +87,19 @@ timescaledb_ProcessUtility(Node *parsetree,
Cache *hcache = hypertable_cache_pin();
Hypertable *hentry = hypertable_cache_get_entry(hcache, relid);

if (hentry != NULL && renamestmt->renameType == OBJECT_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Renaming hypertables is not yet supported")));
if (hentry != NULL && renamestmt->renameType == OBJECT_TABLE) {
executor_level_enter();
spi_hypertable_rename(hentry,
NameStr(hentry->fd.schema_name),
renamestmt->newname);
executor_level_exit();
}
cache_release(hcache);
}
prev_ProcessUtility((Node *) renamestmt, query_string, context, params, dest, completion_tag);
return;
}

if (IsA(parsetree, CopyStmt))
{
/*
Expand Down
118 changes: 95 additions & 23 deletions test/expected/drop_rename_hypertable.out
Original file line number Diff line number Diff line change
Expand Up @@ -317,32 +317,97 @@ Check constraints:
"constraint_8" CHECK (_timescaledb_internal.get_partition_for_key(device_id) >= 0 AND _timescaledb_internal.get_partition_for_key(device_id) < 1073741823)
Inherits: "two_Partitions"

-- Test that renaming hypertable is blocked
\set VERBOSITY default
\set ON_ERROR_STOP 0
-- Test that renaming hypertable works
\d _timescaledb_internal._hyper_1_1_chunk
Table "_timescaledb_internal._hyper_1_1_chunk"
Column | Type | Modifiers
-------------+------------------+-----------
timeCustom | bigint | not null
device_id | text | not null
series_0 | double precision |
series_1 | double precision |
series_2 | double precision |
series_bool | boolean |
Indexes:
"1-two_Partitions_device_id_timeCustom_idx" btree (device_id, "timeCustom" DESC NULLS LAST) WHERE device_id IS NOT NULL
"2-two_Partitions_timeCustom_series_0_idx" btree ("timeCustom" DESC NULLS LAST, series_0) WHERE series_0 IS NOT NULL
"3-two_Partitions_timeCustom_series_1_idx" btree ("timeCustom" DESC NULLS LAST, series_1) WHERE series_1 IS NOT NULL
"4-two_Partitions_timeCustom_series_2_idx" btree ("timeCustom" DESC NULLS LAST, series_2) WHERE series_2 IS NOT NULL
"5-two_Partitions_timeCustom_series_bool_idx" btree ("timeCustom" DESC NULLS LAST, series_bool) WHERE series_bool IS NOT NULL
"6-two_Partitions_timeCustom_device_id_idx" btree ("timeCustom" DESC NULLS LAST, device_id)
"7-two_Partitions_timeCustom_idx" btree ("timeCustom" DESC)
Check constraints:
"constraint_1" CHECK ("timeCustom" >= '1257892416000000000'::bigint AND "timeCustom" < '1257895008000000000'::bigint)
"constraint_2" CHECK (_timescaledb_internal.get_partition_for_key(device_id) >= 1073741823 AND _timescaledb_internal.get_partition_for_key(device_id) < 2147483647)
Inherits: "two_Partitions"

ALTER TABLE "two_Partitions" RENAME TO "newname";
ERROR: Renaming hypertables is not yet supported
\set ON_ERROR_STOP 1
-- Test that renaming ordinary table works
CREATE TABLE renametable (foo int);
ALTER TABLE "renametable" RENAME TO "newname";
SELECT * FROM "newname";
foo
-----
timeCustom | device_id | series_0 | series_1 | series_2 | series_bool
---------------------+-----------+----------+----------+----------+-------------
1257894000000000000 | dev1 | 1.5 | 1 | 2 | t
1257894000000000000 | dev1 | 1.5 | 2 | |
1257894000000001000 | dev1 | 2.5 | 3 | |
1257894001000000000 | dev1 | 3.5 | 4 | |
1257894002000000000 | dev1 | 5.5 | 6 | | t
1257894002000000000 | dev1 | 5.5 | 7 | | f
1257894002000000000 | dev1 | 2.5 | 3 | |
1257897600000000000 | dev1 | 4.5 | 5 | | f
1257987600000000000 | dev1 | 1.5 | 1 | |
1257987600000000000 | dev1 | 1.5 | 2 | |
1257894000000000000 | dev2 | 1.5 | 1 | |
1257894000000000000 | dev2 | 1.5 | 2 | |
(12 rows)

SELECT * FROM _timescaledb_catalog.hypertable;
id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions
----+-------------+------------+------------------------+-------------------------+----------------
1 | public | newname | _timescaledb_internal | _hyper_1 | 2
(1 row)

CREATE SCHEMA "newschema";
ALTER TABLE "newname" SET SCHEMA "newschema";
SELECT * FROM "newschema"."newname";
timeCustom | device_id | series_0 | series_1 | series_2 | series_bool
---------------------+-----------+----------+----------+----------+-------------
1257894000000000000 | dev1 | 1.5 | 1 | 2 | t
1257894000000000000 | dev1 | 1.5 | 2 | |
1257894000000001000 | dev1 | 2.5 | 3 | |
1257894001000000000 | dev1 | 3.5 | 4 | |
1257894002000000000 | dev1 | 5.5 | 6 | | t
1257894002000000000 | dev1 | 5.5 | 7 | | f
1257894002000000000 | dev1 | 2.5 | 3 | |
1257897600000000000 | dev1 | 4.5 | 5 | | f
1257987600000000000 | dev1 | 1.5 | 1 | |
1257987600000000000 | dev1 | 1.5 | 2 | |
1257894000000000000 | dev2 | 1.5 | 1 | |
1257894000000000000 | dev2 | 1.5 | 2 | |
(12 rows)

SELECT * FROM _timescaledb_catalog.hypertable;
id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions
----+-------------+------------+------------------------+-------------------------+----------------
1 | newschema | newname | _timescaledb_internal | _hyper_1 | 2
(1 row)

SELECT * FROM _timescaledb_catalog.hypertable_index WHERE main_schema_name <> 'newschema';
hypertable_id | main_schema_name | main_index_name | definition
---------------+------------------+-----------------+------------
(0 rows)

SELECT * FROM _timescaledb_catalog.chunk_index WHERE main_schema_name <> 'newschema';
id | schema_name | table_name | index_name | main_schema_name | main_index_name | definition
----+-------------+------------+------------+------------------+-----------------+------------
(0 rows)

SELECT * FROM _timescaledb_catalog.hypertable;
id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions
----+-------------+----------------+------------------------+-------------------------+----------------
1 | public | two_Partitions | _timescaledb_internal | _hyper_1 | 2
id | schema_name | table_name | associated_schema_name | associated_table_prefix | num_dimensions
----+-------------+------------+------------------------+-------------------------+----------------
1 | newschema | newname | _timescaledb_internal | _hyper_1 | 2
(1 row)

DROP TABLE "two_Partitions" CASCADE;
DROP TABLE "newschema"."newname" CASCADE;
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table _timescaledb_internal._hyper_1_1_chunk
drop cascades to table _timescaledb_internal._hyper_1_2_chunk
drop cascades to table _timescaledb_internal._hyper_1_3_chunk
drop cascades to table _timescaledb_internal._hyper_1_4_chunk
NOTICE: index "1-two_Partitions_device_id_timeCustom_idx" does not exist, skipping
NOTICE: index "2-two_Partitions_timeCustom_series_0_idx" does not exist, skipping
NOTICE: index "3-two_Partitions_timeCustom_series_1_idx" does not exist, skipping
Expand Down Expand Up @@ -377,11 +442,10 @@ SELECT * FROM _timescaledb_catalog.hypertable;
(0 rows)

\dt "public".*
List of relations
Schema | Name | Type | Owner
--------+---------+-------+----------
public | newname | table | postgres
(1 row)
List of relations
Schema | Name | Type | Owner
--------+------+------+-------
(0 rows)

\dt "_timescaledb_catalog".*
List of relations
Expand All @@ -403,3 +467,11 @@ SELECT * FROM _timescaledb_catalog.hypertable;
--------+------+------+-------+------+-------------
(0 rows)

-- Test that renaming ordinary table works
CREATE TABLE renametable (foo int);
ALTER TABLE "renametable" RENAME TO "newname_none_ht";
SELECT * FROM "newname_none_ht";
foo
-----
(0 rows)