346 lines
13 KiB
PL/PgSQL
346 lines
13 KiB
PL/PgSQL
--------------------------------------------------------------------------------
|
|
-- Private functions
|
|
--------------------------------------------------------------------------------
|
|
|
|
--
|
|
-- Checks if a column is of integer type
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Column_Is_Integer(input_table REGCLASS, colname NAME)
|
|
RETURNS boolean
|
|
AS $$
|
|
BEGIN
|
|
PERFORM atttypid FROM pg_catalog.pg_attribute
|
|
WHERE attrelid = input_table
|
|
AND attname = colname
|
|
AND atttypid IN (SELECT oid FROM pg_type
|
|
WHERE typname IN
|
|
('smallint', 'integer', 'bigint', 'int2', 'int4', 'int8'));
|
|
RETURN FOUND;
|
|
END
|
|
$$
|
|
LANGUAGE PLPGSQL STABLE PARALLEL UNSAFE;
|
|
|
|
--
|
|
-- Checks if a column is of geometry type
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Column_Is_Geometry(input_table REGCLASS, colname NAME)
|
|
RETURNS boolean
|
|
AS $$
|
|
BEGIN
|
|
PERFORM atttypid FROM pg_catalog.pg_attribute
|
|
WHERE attrelid = input_table
|
|
AND attname = colname
|
|
AND atttypid = '@postgisschema@.geometry'::regtype;
|
|
RETURN FOUND;
|
|
END
|
|
$$
|
|
LANGUAGE PLPGSQL STABLE PARALLEL UNSAFE;
|
|
|
|
--
|
|
-- Returns the name of all the columns from a table
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_GetColumns(input_table REGCLASS)
|
|
RETURNS SETOF NAME
|
|
AS $$
|
|
SELECT
|
|
a.attname as "colname"
|
|
FROM pg_catalog.pg_attribute a
|
|
WHERE
|
|
a.attnum > 0
|
|
AND NOT a.attisdropped
|
|
AND a.attrelid = (
|
|
SELECT c.oid
|
|
FROM pg_catalog.pg_class c
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.oid = input_table::oid
|
|
)
|
|
ORDER BY a.attnum;
|
|
$$ LANGUAGE SQL STABLE PARALLEL UNSAFE;
|
|
|
|
--
|
|
-- Returns the id column from a view definition
|
|
-- Note: The id is always of one of this forms:
|
|
-- SELECT t.cartodb_id,
|
|
-- SELECT t.my_id as cartodb_id,
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Get_View_id_column(view_def TEXT)
|
|
RETURNS TEXT
|
|
AS $$
|
|
WITH column_definitions AS
|
|
(
|
|
SELECT regexp_split_to_array(trim(leading from regexp_split_to_table(view_def, '\n')), ' ') AS col_def
|
|
)
|
|
SELECT trim(trailing ',' FROM split_part(col_def[2], '.', 2))
|
|
FROM column_definitions
|
|
WHERE trim(trailing ',' FROM col_def[array_length(col_def, 1)]) IN ('t.cartodb_id', 'cartodb_id')
|
|
LIMIT 1;
|
|
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
|
|
|
--
|
|
-- Returns the geom column from a view definition
|
|
--
|
|
-- Note: The the_geom is always of one of this forms:
|
|
-- t.the_geom,
|
|
-- t.my_geom as the_geom,
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Get_View_geom_column(view_def TEXT)
|
|
RETURNS TEXT
|
|
AS $$
|
|
WITH column_definitions AS
|
|
(
|
|
SELECT regexp_split_to_array(trim(leading from regexp_split_to_table(view_def, '\n')), ' ') AS col_def
|
|
)
|
|
SELECT trim(trailing ',' FROM split_part(col_def[1], '.', 2))
|
|
FROM column_definitions
|
|
WHERE trim(trailing ',' FROM col_def[array_length(col_def, 1)]) IN ('t.the_geom', 'the_geom')
|
|
LIMIT 1;
|
|
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
|
|
|
--
|
|
-- Returns the webmercatorcolumn from a view definition
|
|
-- Note: The the_geom_webmercator is always of one of this forms:
|
|
-- t.the_geom_webmercator,
|
|
-- t.my_geom as the_geom_webmercator,
|
|
-- Or without the trailing comma:
|
|
-- t.the_geom_webmercator
|
|
-- t.my_geom as the_geom_webmercator
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Get_View_webmercator_column(view_def TEXT)
|
|
RETURNS TEXT
|
|
AS $$
|
|
WITH column_definitions AS
|
|
(
|
|
SELECT regexp_split_to_array(trim(leading from regexp_split_to_table(view_def, '\n')), ' ') AS col_def
|
|
)
|
|
SELECT trim(trailing ',' FROM split_part(col_def[1], '.', 2))
|
|
FROM column_definitions
|
|
WHERE trim(trailing ',' FROM col_def[array_length(col_def, 1)]) IN ('t.the_geom_webmercator', 'the_geom_webmercator')
|
|
LIMIT 1;
|
|
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
|
|
|
|
|
|
--
|
|
-- List all registered tables in a server + schema
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_List_Registered_Tables(
|
|
server_internal NAME,
|
|
remote_schema TEXT
|
|
)
|
|
RETURNS TABLE(
|
|
registered boolean,
|
|
remote_table TEXT,
|
|
local_qualified_name TEXT,
|
|
id_column_name TEXT,
|
|
geom_column_name TEXT,
|
|
webmercator_column_name TEXT
|
|
)
|
|
AS $$
|
|
DECLARE
|
|
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
|
BEGIN
|
|
RETURN QUERY SELECT
|
|
true as registered,
|
|
source_table::text as remote_table,
|
|
format('%I.%I', dependent_schema, dependent_view)::text as local_qualified_name,
|
|
@extschema@.__CDB_FS_Get_View_id_column(view_definition) as id_column_name,
|
|
@extschema@.__CDB_FS_Get_View_geom_column(view_definition) as geom_column_name,
|
|
@extschema@.__CDB_FS_Get_View_webmercator_column(view_definition) as webmercator_column_name
|
|
FROM
|
|
(
|
|
SELECT DISTINCT
|
|
dependent_ns.nspname as dependent_schema,
|
|
dependent_view.relname as dependent_view,
|
|
source_table.relname as source_table,
|
|
pg_get_viewdef(dependent_view.oid) as view_definition
|
|
FROM pg_depend
|
|
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
|
|
JOIN pg_class as dependent_view ON pg_rewrite.ev_class = dependent_view.oid
|
|
JOIN pg_class as source_table ON pg_depend.refobjid = source_table.oid
|
|
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
|
|
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
|
|
WHERE
|
|
source_ns.nspname = local_schema
|
|
ORDER BY 1,2
|
|
) _aux;
|
|
END
|
|
$$
|
|
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Public functions
|
|
--------------------------------------------------------------------------------
|
|
|
|
--
|
|
-- Sets up a Federated Table
|
|
--
|
|
-- Precondition: the federated server has to be set up via
|
|
-- CDB_Federated_Server_Register_PG
|
|
--
|
|
-- Postcondition: it generates a view in the schema of the user that
|
|
-- can be used through SQL and Maps API's.
|
|
-- If the table was already exported, it will be dropped and re-imported
|
|
--
|
|
-- The view is placed under the server's view schema (cdb_fs_$(server_name))
|
|
--
|
|
-- E.g:
|
|
-- SELECT cartodb.CDB_SetUp_PG_Federated_Table(
|
|
-- 'amazon', -- mandatory, name of the federated server
|
|
-- 'my_remote_schema', -- mandatory, schema name
|
|
-- 'my_remote_table', -- mandatory, table name
|
|
-- 'id', -- mandatory, name of the id column
|
|
-- 'geom', -- optional, name of the geom column, preferably in 4326
|
|
-- 'webmercator' -- optional, should be in 3857 if present
|
|
-- 'local_name' -- optional, name of the local view (uses the remote_name if not declared)
|
|
-- );
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Table_Register(
|
|
server TEXT,
|
|
remote_schema TEXT,
|
|
remote_table TEXT,
|
|
id_column TEXT,
|
|
geom_column TEXT DEFAULT NULL,
|
|
webmercator_column TEXT DEFAULT NULL,
|
|
local_name NAME DEFAULT NULL
|
|
)
|
|
RETURNS void
|
|
AS $$
|
|
DECLARE
|
|
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => false);
|
|
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
|
role_name name := @extschema@.__CDB_FS_Generate_Server_Role_Name(server_internal);
|
|
|
|
src_table REGCLASS; -- import_schema.remote_table - import_schema is local_schema
|
|
local_view text; -- view_schema.local_name - view_schema is server_internal
|
|
|
|
rest_of_cols TEXT[];
|
|
geom_expression TEXT;
|
|
webmercator_expression TEXT;
|
|
carto_columns_expression TEXT[];
|
|
BEGIN
|
|
IF remote_table IS NULL THEN
|
|
RAISE EXCEPTION 'Remote table name cannot be NULL';
|
|
END IF;
|
|
|
|
-- Make do with whatever columns are provided
|
|
IF webmercator_column IS NULL THEN
|
|
webmercator_column := geom_column;
|
|
ELSIF geom_column IS NULL THEN
|
|
geom_column := webmercator_column;
|
|
END IF;
|
|
|
|
IF local_name IS NULL THEN
|
|
local_name := remote_table;
|
|
END IF;
|
|
|
|
-- Import the foreign table
|
|
-- Drop the old view / table if there was one
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = local_schema AND table_name = remote_table) THEN
|
|
EXECUTE @extschema@.CDB_Federated_Table_Unregister(server, remote_schema, remote_table);
|
|
END IF;
|
|
BEGIN
|
|
EXECUTE FORMAT('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I;',
|
|
remote_schema, remote_table, server_internal, local_schema);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RAISE EXCEPTION 'Could not import schema "%" of server "%": %', remote_schema, server, SQLERRM;
|
|
END;
|
|
|
|
BEGIN
|
|
src_table := format('%I.%I', local_schema, remote_table);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RAISE EXCEPTION 'Could not import table "%.%" of server "%"', remote_schema, remote_table, server;
|
|
END;
|
|
|
|
-- Check id_column is numeric
|
|
IF NOT @extschema@.__CDB_FS_Column_Is_Integer(src_table, id_column) THEN
|
|
RAISE EXCEPTION 'non integer id_column "%"', id_column;
|
|
END IF;
|
|
|
|
-- Check if the geom and mercator columns have a geometry type (if provided)
|
|
IF geom_column IS NOT NULL AND NOT @extschema@.__CDB_FS_Column_Is_Geometry(src_table, geom_column) THEN
|
|
RAISE EXCEPTION 'non geometry column "%"', geom_column;
|
|
END IF;
|
|
IF webmercator_column IS NOT NULL AND NOT @extschema@.__CDB_FS_Column_Is_Geometry(src_table, webmercator_column) THEN
|
|
RAISE EXCEPTION 'non geometry column "%"', webmercator_column;
|
|
END IF;
|
|
|
|
-- Get a list of columns excluding the id, geom and the_geom_webmercator
|
|
SELECT ARRAY(
|
|
SELECT quote_ident(c) FROM @extschema@.__CDB_FS_GetColumns(src_table) AS c
|
|
WHERE c NOT IN (SELECT * FROM (SELECT unnest(ARRAY[id_column, geom_column, webmercator_column, 'cartodb_id', 'the_geom', 'the_geom_webmercator']) col) carto WHERE carto.col IS NOT NULL)
|
|
) INTO rest_of_cols;
|
|
|
|
IF geom_column IS NULL
|
|
THEN
|
|
geom_expression := 'NULL AS the_geom';
|
|
ELSIF @postgisschema@.Find_SRID(local_schema::varchar, remote_table::varchar, geom_column::varchar) = 4326
|
|
THEN
|
|
geom_expression := format('t.%I AS the_geom', geom_column);
|
|
ELSE
|
|
-- It needs an ST_Transform to 4326
|
|
geom_expression := format('@postgisschema@.ST_Transform(t.%I,4326) AS the_geom', geom_column);
|
|
END IF;
|
|
|
|
IF webmercator_column IS NULL
|
|
THEN
|
|
webmercator_expression := 'NULL AS the_geom_webmercator';
|
|
ELSIF @postgisschema@.Find_SRID(local_schema::varchar, remote_table::varchar, webmercator_column::varchar) = 3857
|
|
THEN
|
|
webmercator_expression := format('t.%I AS the_geom_webmercator', webmercator_column);
|
|
ELSE
|
|
-- It needs an ST_Transform to 3857
|
|
webmercator_expression := format('@postgisschema@.ST_Transform(t.%I,3857) AS the_geom_webmercator', webmercator_column);
|
|
END IF;
|
|
|
|
-- CARTO columns expressions
|
|
carto_columns_expression := ARRAY[
|
|
format('t.%1$I AS cartodb_id', id_column),
|
|
geom_expression,
|
|
webmercator_expression
|
|
];
|
|
|
|
-- Create view schema if it doesn't exist
|
|
IF NOT EXISTS (SELECT oid FROM pg_namespace WHERE nspname = server_internal) THEN
|
|
EXECUTE 'CREATE SCHEMA ' || quote_ident(server_internal) || ' AUTHORIZATION ' || quote_ident(role_name);
|
|
END IF;
|
|
|
|
-- Create a view with homogeneous CDB fields
|
|
BEGIN
|
|
EXECUTE format(
|
|
'CREATE OR REPLACE VIEW %1$I.%2$I AS
|
|
SELECT %3s
|
|
FROM %4$s t',
|
|
server_internal, local_name,
|
|
array_to_string(carto_columns_expression || rest_of_cols, ','),
|
|
src_table
|
|
);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
IF EXISTS (SELECT to_regclass(format('%1$I.%2$I', server_internal, local_name))) THEN
|
|
RAISE EXCEPTION 'Could not import table "%" as "%.%" already exists', remote_table, server_internal, local_name;
|
|
ELSE
|
|
RAISE EXCEPTION 'Could not import table "%" as "%": %', remote_table, local_name, SQLERRM;
|
|
END IF;
|
|
END;
|
|
END
|
|
$$
|
|
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|
|
|
|
--
|
|
-- Unregisters a remote table. Any dependent object will be dropped
|
|
--
|
|
CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Table_Unregister(
|
|
server TEXT,
|
|
remote_schema TEXT,
|
|
remote_table TEXT
|
|
)
|
|
RETURNS void
|
|
AS $$
|
|
DECLARE
|
|
server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => false);
|
|
local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema);
|
|
BEGIN
|
|
EXECUTE FORMAT ('DROP FOREIGN TABLE %I.%I CASCADE;', local_schema, remote_table);
|
|
END
|
|
$$
|
|
LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE;
|