initial commit

This commit is contained in:
2025-05-20 09:24:29 -04:00
commit 366d7865ba
142 changed files with 25396 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# to avoid setuptools/distutils bug
+274
View File
@@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
"""Test audit columns"""
import pytest
from pyrseas.testutils import AugmentToMapTestCase
CREATE_STMT = "CREATE TABLE t1 (c1 integer, c2 text)"
FUNC_SRC1 = """
BEGIN
NEW.modified_by_user = SESSION_USER;
NEW.modified_timestamp = CURRENT_TIMESTAMP;
RETURN NEW;
END"""
FUNC_SRC2 = """
BEGIN
NEW.updated = CURRENT_TIMESTAMP;
RETURN NEW;
END"""
class AuditColumnsTestCase(AugmentToMapTestCase):
"""Test mapping of audit column augmentations"""
def test_predef_column(self):
"Add predefined audit column"
augmap = {'schema sd': {'table t1': {
'audit_columns': 'created_date_only'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'created_date': {'type': 'date', 'not_null': True,
'default': "('now'::text)::date"}}]}
assert expmap == dbmap['schema sd']['table t1']
def test_unknown_table(self):
"Error on non-existent table"
augmap = {'schema sd': {'table t2': {
'audit_columns': 'created_date_only'}}}
with pytest.raises(KeyError):
self.to_map([CREATE_STMT], augmap)
def test_bad_audit_spec(self):
"Error on bad audit column specification"
augmap = {'schema sd': {'table t1': {
'audit_column': 'created_date_only'}}}
with pytest.raises(KeyError):
self.to_map([CREATE_STMT], augmap)
def test_unknown_audit_spec(self):
"Error on non-existent audit column specification"
augmap = {'schema sd': {'table t1': {
'audit_columns': 'created_date'}}}
with pytest.raises(KeyError):
self.to_map([CREATE_STMT], augmap)
def test_new_column(self):
"Add new (non-predefined) audit column"
augmap = {'augmenter': {'columns': {
'modified_date': {'type': 'date', 'not_null': True,
'default': "('now'::text)::date"}},
'audit_columns': {'modified_date_only': {
'columns': ['modified_date']}}},
'schema sd': {'table t1': {
'audit_columns': 'modified_date_only'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_date': {'type': 'date', 'not_null': True,
'default': "('now'::text)::date"}}]}
assert expmap == dbmap['schema sd']['table t1']
def test_rename_column(self):
"Add predefined audit column but with new name"
augmap = {'augmenter': {'columns': {
'modified_timestamp': {'name': 'updated'}}},
'schema sd': {'table t1': {
'audit_columns': 'modified_only'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
colmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'updated': {'type': 'timestamp with time zone',
'not_null': True}}],
'triggers': {'t1_20_audit_modified_only': {
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.audit_modified', 'timing': 'before'}}}
funcmap = {'language': 'plpgsql', 'returns': 'trigger',
'security_definer': True, 'description':
'Provides modified_timestamp values for audit columns.',
'source': FUNC_SRC2}
assert dbmap['schema sd']['table t1'] == colmap
assert dbmap['schema sd']['function audit_modified()'] == funcmap
def test_change_column_type(self):
"Add predefined audit column but with changed datatype"
augmap = {'augmenter': {'columns': {'created_date': {'type': 'text'}}},
'schema sd': {'table t1': {
'audit_columns': 'created_date_only'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'created_date': {'type': 'text', 'not_null': True,
'default': "('now'::text)::date"}}]}
assert expmap == dbmap['schema sd']['table t1']
def test_columns_with_trigger(self):
"Add predefined audit columns with trigger"
augmap = {'schema sd': {'table t1': {'audit_columns': 'default'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_by_user': {'type': 'character varying(63)',
'not_null': True}},
{'modified_timestamp': {'type': 'timestamp with time zone',
'not_null': True}}],
'triggers': {'t1_20_audit_default': {
'events': ['update'], 'level': 'row',
'procedure': 'sd.audit_default', 'timing': 'before'}}}
assert expmap == dbmap['schema sd']['table t1']
assert dbmap['schema sd']['function audit_default()'][
'returns'] == 'trigger'
assert dbmap['schema sd']['function audit_default()'][
'source'] == FUNC_SRC1
def test_nondefault_schema_with_trigger(self):
"Add predefined audit columns with trigger in a non-default schema"
stmts = ["CREATE SCHEMA s1",
"CREATE TABLE s1.t1 (c1 integer, c2 text)"]
augmap = {'schema s1': {'table t1': {'audit_columns': 'default'}}}
dbmap = self.to_map(stmts, augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_by_user': {'type': 'character varying(63)',
'not_null': True}},
{'modified_timestamp': {'type': 'timestamp with time zone',
'not_null': True}}],
'triggers': {'t1_20_audit_default': {
'events': ['update'], 'level': 'row',
'procedure': 's1.audit_default', 'timing': 'before'}}}
assert expmap == dbmap['schema s1']['table t1']
assert dbmap['schema s1']['function audit_default()']['returns'] == \
'trigger'
assert dbmap['schema s1']['function audit_default()'][
'source'] == FUNC_SRC1
def test_skip_existing_columns(self):
"Do not add already existing audit columns"
stmts = [CREATE_STMT,
"ALTER TABLE t1 ADD modified_by_user varchar(63) NOT NULL",
"ALTER TABLE t1 ADD modified_timestamp "
"timestamp with time zone NOT NULL"]
augmap = {'schema sd': {'table t1': {
'audit_columns': 'default'}}}
dbmap = self.to_map(stmts, augmap)
expmap = [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_by_user': {'type': 'character varying(63)',
'not_null': True}},
{'modified_timestamp': {'type': 'timestamp with time zone',
'not_null': True}}]
assert expmap == dbmap['schema sd']['table t1']['columns']
def test_change_existing_columns(self):
"Change already existing audit columns"
stmts = [CREATE_STMT, "ALTER TABLE t1 ADD modified_by_user text ",
"ALTER TABLE t1 ADD modified_timestamp "
"timestamp with time zone NOT NULL"]
augmap = {'schema sd': {'table t1': {'audit_columns': 'default'}}}
dbmap = self.to_map(stmts, augmap)
expmap = [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_by_user': {'type': 'character varying(63)',
'not_null': True}},
{'modified_timestamp': {'type': 'timestamp with time zone',
'not_null': True}}]
assert expmap == dbmap['schema sd']['table t1']['columns']
def test_custom_function_template(self):
"Add new (non-predefined) audit trigger using a function template"
template = """
BEGIN
NEW.{{modified_by_user}} = SESSION_USER;
NEW.{{modified_timestamp}} = CURRENT_TIMESTAMP::timestamp(0);
RETURN NEW;
END"""
source = """
BEGIN
NEW.modified_by_user = SESSION_USER;
NEW.modified_timestamp = CURRENT_TIMESTAMP::timestamp(0);
RETURN NEW;
END"""
augmap = {
'augmenter': {
'audit_columns': {'custom': {
'columns': ['modified_by_user', 'modified_timestamp'],
'triggers': ['custom_audit']}},
'function_templates': {'custom_template': template},
'functions': {'custom_audit()': {
'description': 'Maintain custom audit columns',
'language': 'plpgsql',
'returns': 'trigger',
'security_definer': True,
'source': '{{custom_template}}'}},
'triggers': {'custom_audit': {
'events': ['insert', 'update'],
'level': 'row',
'name': '{{table_name}}_20_custom_audit',
'procedure': 'custom_audit',
'timing': 'before'}}},
'schema sd': {'table t1': {
'audit_columns': 'custom'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_by_user': {'type': 'character varying(63)',
'not_null': True}},
{'modified_timestamp': {'type': 'timestamp with time zone',
'not_null': True}}],
'triggers': {'t1_20_custom_audit': {
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.custom_audit', 'timing': 'before'}}}
assert expmap == dbmap['schema sd']['table t1']
assert dbmap['schema sd']['function custom_audit()'][
'returns'] == 'trigger'
assert dbmap['schema sd']['function custom_audit()'][
'source'] == source
def test_custom_function_inline_with_column_substitution(self):
"Add new (non-predefined) audit trigger using an inline definition"
template = """
BEGIN
NEW.{{modified_by_user}} = SESSION_USER;
NEW.{{modified_timestamp}} = CURRENT_TIMESTAMP::timestamp(0);
RETURN NEW;
END"""
source = """
BEGIN
NEW.modified_by_user = SESSION_USER;
NEW.modified_timestamp = CURRENT_TIMESTAMP::timestamp(0);
RETURN NEW;
END"""
augmap = {
'augmenter': {
'audit_columns': {'custom': {
'columns': ['modified_by_user', 'modified_timestamp'],
'triggers': ['custom_audit']}},
'functions': {'custom_audit()': {
'description': 'Maintain custom audit columns',
'language': 'plpgsql',
'returns': 'trigger',
'security_definer': True,
'source': template}},
'triggers': {'custom_audit': {
'events': ['insert', 'update'],
'level': 'row',
'name': '{{table_name}}_20_custom_audit',
'procedure': 'custom_audit',
'timing': 'before'}}},
'schema sd': {'table t1': {
'audit_columns': 'custom'}}}
dbmap = self.to_map([CREATE_STMT], augmap)
expmap = {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'modified_by_user': {'type': 'character varying(63)',
'not_null': True}},
{'modified_timestamp': {'type': 'timestamp with time zone',
'not_null': True}}],
'triggers': {'t1_20_custom_audit': {
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.custom_audit', 'timing': 'before'}}}
assert expmap == dbmap['schema sd']['table t1']
assert dbmap['schema sd']['function custom_audit()'][
'returns'] == 'trigger'
assert dbmap['schema sd']['function custom_audit()'][
'source'] == source
+1
View File
@@ -0,0 +1 @@
"""Pyrseas dbobject unit tests"""
+171
View File
@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-
"""Test casts"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
SOURCE = "SELECT CAST($1::int AS boolean)"
CREATE_FUNC = "CREATE FUNCTION int2_bool(smallint) RETURNS boolean " \
"LANGUAGE sql IMMUTABLE AS $_$%s$_$" % SOURCE
CREATE_DOMAIN = "CREATE DOMAIN d1 AS integer"
CREATE_STMT1 = "CREATE CAST (smallint AS boolean) WITH FUNCTION " \
"sd.int2_bool(smallint)"
CREATE_STMT3 = "CREATE CAST (d1 AS integer) WITH INOUT AS IMPLICIT"
DROP_STMT = "DROP CAST IF EXISTS (smallint AS boolean)"
COMMENT_STMT = "COMMENT ON CAST (smallint AS boolean) IS 'Test cast 1'"
class CastToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing casts"""
def test_map_cast_function(self):
"Map a cast with a function"
dbmap = self.to_map([CREATE_FUNC, CREATE_STMT1], superuser=True)
expmap = {'function': 'sd.int2_bool(smallint)', 'context': 'explicit',
'method': 'function'}
assert dbmap['cast (smallint as boolean)'] == expmap
def test_map_cast_inout(self):
"Map a cast with INOUT"
dbmap = self.to_map([CREATE_DOMAIN, CREATE_STMT3])
expmap = {'context': 'implicit', 'method': 'inout',
'depends_on': ['domain d1']}
assert dbmap['cast (sd.d1 as integer)'] == expmap
def test_map_cast_comment(self):
"Map a cast comment"
dbmap = self.to_map([CREATE_FUNC, CREATE_STMT1, COMMENT_STMT],
superuser=True)
assert dbmap['cast (smallint as boolean)']['description'] == \
'Test cast 1'
class CastToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input casts"""
def test_create_cast_function(self):
"Create a cast with a function"
stmts = [DROP_STMT, CREATE_FUNC]
inmap = self.std_map()
inmap.update({'cast (smallint as boolean)': {
'function': 'sd.int2_bool(smallint)', 'context': 'explicit',
'method': 'function'}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == CREATE_STMT1
def test_create_cast_inout(self):
"Create a cast with INOUT"
stmts = [CREATE_DOMAIN, "DROP CAST IF EXISTS (d1 AS integer)"]
inmap = self.std_map()
inmap.update({'cast (d1 as integer)': {
'context': 'implicit', 'method': 'inout'}})
inmap['schema sd'].update({'domain d1': {'type': 'integer'}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == CREATE_STMT3
def test_create_cast_schema(self):
"Create a cast using a type/domain in a non-default schema"
stmts = ["CREATE SCHEMA s1", "CREATE DOMAIN s1.d1 AS integer",
"DROP CAST IF EXISTS (integer AS s1.d1)"]
inmap = self.std_map()
inmap.update({'cast (integer as s1.d1)': {
'context': 'assignment', 'method': 'binary coercible'}})
inmap.update({'schema s1': {'domain d1': {'type': 'integer'}}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == "CREATE CAST (integer AS s1.d1) " \
"WITHOUT FUNCTION AS ASSIGNMENT"
def test_bad_cast_map(self):
"Error creating a cast with a bad map"
inmap = self.std_map()
inmap.update({'(smallint as boolean)': {
'function': 'int2_bool(smallint)', 'context': 'explicit',
'method': 'function'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_cast(self):
"Drop an existing cast"
stmts = [DROP_STMT, CREATE_FUNC, CREATE_STMT1]
sql = self.to_sql(self.std_map(), stmts, superuser=True)
assert sql[0] == "DROP CAST (smallint AS boolean)"
def test_cast_with_comment(self):
"Create a cast with a comment"
inmap = self.std_map()
inmap.update({'cast (smallint as boolean)': {
'description': 'Test cast 1', 'function': 'sd.int2_bool(smallint)',
'context': 'explicit', 'method': 'function'}})
inmap['schema sd'].update({'function int2_bool(smallint)': {
'returns': 'boolean', 'language': 'sql', 'immutable': True,
'source': SOURCE}})
sql = self.to_sql(inmap, [DROP_STMT])
# sql[0:1] -> SET, CREATE FUNCTION
assert fix_indent(sql[2]) == CREATE_STMT1
assert sql[3] == COMMENT_STMT
def test_comment_on_cast(self):
"Create a comment for an existing cast"
stmts = [DROP_STMT, CREATE_FUNC, CREATE_STMT1]
inmap = self.std_map()
inmap.update({'cast (smallint as boolean)': {
'description': 'Test cast 1', 'function': 'int2_bool(smallint)',
'context': 'explicit', 'method': 'function'}})
inmap['schema sd'].update({'function int2_bool(smallint)': {
'returns': 'boolean', 'language': 'sql', 'immutable': True,
'source': SOURCE}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == [COMMENT_STMT]
def test_drop_cast_comment(self):
"Drop a comment on an existing cast"
stmts = [DROP_STMT, CREATE_FUNC, CREATE_STMT1, COMMENT_STMT]
inmap = self.std_map()
inmap.update({'cast (smallint as boolean)': {
'function': 'int2_bool(smallint)', 'context': 'explicit',
'method': 'function'}})
inmap['schema sd'].update({'function int2_bool(smallint)': {
'returns': 'boolean', 'language': 'sql', 'immutable': True,
'source': SOURCE}})
assert self.to_sql(inmap, stmts, superuser=True) == \
["COMMENT ON CAST (smallint AS boolean) IS NULL"]
def test_change_cast_comment(self):
"Change existing comment on a cast"
stmts = [DROP_STMT, CREATE_FUNC, CREATE_STMT1, COMMENT_STMT]
inmap = self.std_map()
inmap.update({'cast (smallint as boolean)': {
'description': 'Changed cast 1', 'function': 'int2_bool(smallint)',
'context': 'explicit', 'method': 'function'}})
inmap['schema sd'].update({'function int2_bool(smallint)': {
'returns': 'boolean', 'language': 'sql', 'immutable': True,
'source': SOURCE}})
assert self.to_sql(inmap, stmts, superuser=True) == \
["COMMENT ON CAST (smallint AS boolean) IS 'Changed cast 1'"]
def test_cast_function_view_depends(self):
"Cast that depends on a function that depends on a view. See #86"
stmts = ["CREATE TABLE t1 (id integer)"]
inmap = self.std_map()
inmap.update({'cast (sd.v1 as sd.t1)': {
'context': 'explicit', 'function': 'sd.v1_to_t1(sd.v1)',
'method': 'function'}})
inmap['schema sd'].update({
'function v1_to_t1(sd.v1)': {
'returns': 'sd.t1', 'language': 'plpgsql',
'source': "\nDECLARE o sd.t1;\nBEGIN o:= ROW($1.id)::t1;\n"
"RETURN o;\nEND"},
'table t1': {'columns': [{'id': {'type': 'integer'}}]},
'view v1': {'definition': " SELECT t1.id\n FROM sd.t1;",
'depends_on': ['table t1']}})
sql = self.to_sql(inmap, stmts)
assert len(sql) == 3
assert fix_indent(sql[0]) == \
"CREATE VIEW sd.v1 AS SELECT t1.id FROM sd.t1"
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.v1_to_t1(sd.v1) " \
"RETURNS sd.t1 LANGUAGE plpgsql AS $_$\nDECLARE o sd.t1;\n" \
"BEGIN o:= ROW($1.id)::t1;\nRETURN o;\nEND$_$"
assert fix_indent(sql[2]) == "CREATE CAST (sd.v1 AS sd.t1) WITH " \
"FUNCTION sd.v1_to_t1(sd.v1)"
+132
View File
@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""Test collations
These tests require that the locale fr_FR.utf8 (or equivalent) be installed.
"""
import sys
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
if sys.platform == 'win32':
COLL = 'French_France.1252'
else:
COLL = 'fr_FR.UTF-8'
CREATE_STMT = "CREATE COLLATION sd.c1 (LC_COLLATE = '%s', LC_CTYPE = '%s')" % (
COLL, COLL)
COMMENT_STMT = "COMMENT ON COLLATION sd.c1 IS 'Test collation c1'"
class CollationToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing collations"""
def test_map_collation1(self):
"Map a collation"
dbmap = self.to_map([CREATE_STMT])
expmap = {'lc_collate': COLL, 'lc_ctype': COLL}
assert dbmap['schema sd']['collation c1'] == expmap
def test_map_collation_comment(self):
"Map a collation comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['collation c1']['description'] == \
'Test collation c1'
def test_map_column_collation(self):
"Map a table with a column collation"
dbmap = self.to_map(
[CREATE_STMT, "CREATE TABLE t1 (c1 integer, c2 text COLLATE c1)"])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text', 'collation': 'c1'}}],
'depends_on': ['collation c1']}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_index_collation(self):
"Map an index with column collation"
stmts = [CREATE_STMT, "CREATE TABLE t1 (c1 integer, c2 text)",
"CREATE INDEX t1_idx ON t1 (c2 COLLATE c1)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {
'keys': [{'c2': {'collation': 'sd.c1'}}],
'depends_on': ['collation c1']}}}
assert dbmap['schema sd']['table t1'] == expmap
class CollationToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input collations"""
def test_create_collation1(self):
"Create a collation"
inmap = self.std_map()
inmap['schema sd'].update({'collation c1': {
'lc_collate': COLL, 'lc_ctype': COLL}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
def test_create_collation_schema(self):
"Create a collation in a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'collation c1': {
'lc_collate': COLL, 'lc_ctype': COLL}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE COLLATION s1.c1 (" \
"LC_COLLATE = '%s', LC_CTYPE = '%s')" % (COLL, COLL)
def test_bad_collation_map(self):
"Error creating a collation with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'c1': {
'lc_collate': COLL, 'lc_ctype': COLL}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_collation(self):
"Drop an existing collation"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql[0] == "DROP COLLATION sd.c1"
def test_collation_with_comment(self):
"Create a collation with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'collation c1': {
'description': 'Test collation c1',
'lc_collate': COLL, 'lc_ctype': COLL}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_create_table_column_collation(self):
"Create a table with a column with non-default collation"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'text', 'collation': 'c1'}}]}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TABLE sd.t1 (" \
'c1 integer NOT NULL, c2 text COLLATE "c1")'
def test_create_index_collation(self):
"Create an index with column collation"
stmts = [CREATE_STMT, "CREATE TABLE t1 (c1 integer, c2 text)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': [{'c2': {'collation': 'c1'}}]}}}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == \
"CREATE INDEX t1_idx ON sd.t1 (c2 COLLATE c1)"
def test_create_type_attribute_collation(self):
"Create a composite type with an attribute with non-default collation"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'text', 'collation': 'c1'}}]}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TYPE sd.t1 AS (x integer, " \
'y text COLLATE "c1")'
+410
View File
@@ -0,0 +1,410 @@
# -*- coding: utf-8 -*-
"""Test columns"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
TYPELIST = [
('SMALLINT', 'smallint'),
('INTEGER', 'integer'),
('BIGINT', 'bigint'),
('int', 'integer'),
('int2', 'smallint'),
('int4', 'integer'),
('int8', 'bigint'),
('NUMERIC', 'numeric'),
('NUMERIC(1)', 'numeric(1,0)'),
('NUMERIC(12)', 'numeric(12,0)'),
('NUMERIC(1000)', 'numeric(1000,0)'),
('NUMERIC(12,2)', 'numeric(12,2)'),
('NUMERIC(1000,500)', 'numeric(1000,500)'),
('DECIMAL', 'numeric'),
('dec(9,5)', 'numeric(9,5)'),
('REAL', 'real'),
('DOUBLE PRECISION', 'double precision'),
('FLOAT', 'double precision'),
('FLOAT(1)', 'real'),
('FLOAT(24)', 'real'),
('FLOAT(25)', 'double precision'),
('FLOAT(53)', 'double precision'),
# SERIAL and BIGSERIAL have side effects
('MONEY', 'money'),
('CHARACTER(1)', 'character(1)'),
('CHARACTER VARYING(200000)', 'character varying(200000)'),
('CHAR(16)', 'character(16)'),
('VARCHAR(256)', 'character varying(256)'),
('TEXT', 'text'),
('CHAR', 'character(1)'),
('CHARACTER VARYING', 'character varying'),
('"char"', '"char"'),
('name', 'name'),
('bytea', 'bytea'),
('DATE', 'date'),
('TIME', 'time without time zone'),
('TIME WITHOUT TIME ZONE', 'time without time zone'),
('TIME WITH TIME ZONE', 'time with time zone'),
('TIMESTAMP', 'timestamp without time zone'),
('TIMESTAMP WITHOUT TIME ZONE', 'timestamp without time zone'),
('TIMESTAMP WITH TIME ZONE', 'timestamp with time zone'),
('TIME(0)', 'time(0) without time zone'),
('TIME(1) WITHOUT TIME ZONE', 'time(1) without time zone'),
('TIME(2) WITH TIME ZONE', 'time(2) with time zone'),
('TIMESTAMP(3)', 'timestamp(3) without time zone'),
('TIMESTAMP(4) WITHOUT TIME ZONE', 'timestamp(4) without time zone'),
('TIMESTAMP(5) WITH TIME ZONE', 'timestamp(5) with time zone'),
('INTERVAL', 'interval'),
('INTERVAL(6)', 'interval(6)'),
('INTERVAL YEAR', 'interval year'),
('INTERVAL MONTH', 'interval month'),
('INTERVAL DAY', 'interval day'),
('INTERVAL HOUR', 'interval hour'),
('INTERVAL MINUTE', 'interval minute'),
('INTERVAL SECOND', 'interval second'),
('INTERVAL YEAR TO MONTH', 'interval year to month'),
('INTERVAL DAY TO HOUR', 'interval day to hour'),
('INTERVAL DAY TO MINUTE', 'interval day to minute'),
('INTERVAL DAY TO SECOND', 'interval day to second'),
('INTERVAL HOUR TO MINUTE', 'interval hour to minute'),
('INTERVAL HOUR TO SECOND', 'interval hour to second'),
('INTERVAL MINUTE TO SECOND', 'interval minute to second'),
('INTERVAL SECOND(3)', 'interval second(3)'),
('INTERVAL HOUR TO SECOND(5)', 'interval hour to second(5)'),
('BOOLEAN', 'boolean'),
('POINT', 'point'),
('LINE', 'line'),
('LSEG', 'lseg'),
('BOX', 'box'),
('PATH', 'path'),
('POLYGON', 'polygon'),
('CIRCLE', 'circle'),
('cidr', 'cidr'),
('inet', 'inet'),
('macaddr', 'macaddr'),
('BIT(2)', 'bit(2)'),
('BIT VARYING(100)', 'bit varying(100)'),
('BIT', 'bit(1)'),
('BIT VARYING', 'bit varying'),
('tsvector', 'tsvector'),
('tsquery', 'tsquery'),
('UUID', 'uuid'),
('XML', 'xml'),
('JSON', 'json'),
('JSONB', 'jsonb')]
CREATE_STMT1 = "CREATE TABLE t1 (c1 integer, c2 text)"
CREATE_STMT2 = "CREATE TABLE t1 (c1 integer, c2 text, c3 date)"
CREATE_STMT3 = "CREATE TABLE t1 (c1 integer, c2 text, c3 date, c4 text)"
DROP_COL_STMT = "ALTER TABLE t1 DROP COLUMN c3"
class ColumnToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of column-related elements in created tables"""
def test_data_types(self):
"Map a table with columns for (almost) each native PostgreSQL type"
colstab = []
colsmap = []
for colnum, (coltype, maptype) in enumerate(TYPELIST):
col = "c%d" % (colnum + 1)
colstab.append("%s %s" % (col, coltype))
if coltype == 'name' and self.db.version >= 120000:
colsmap.append({col: {'type': maptype, 'collation': 'C'}})
else:
colsmap.append({col: {'type': maptype}})
dbmap = self.to_map(["CREATE TABLE t1 (%s)" % ", ".join(colstab)])
expmap = {'columns': colsmap}
assert dbmap['schema sd']['table t1'] == expmap
def test_not_null(self):
"Map a table with a NOT NULL column"
stmts = ["CREATE TABLE t1 (c1 INTEGER, c2 INTEGER NULL, "
"c3 INTEGER NOT NULL)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'integer'}},
{'c3': {'type': 'integer', 'not_null': True}}]}
assert dbmap['schema sd']['table t1'] == expmap
def test_column_defaults(self):
"Map a table with various types and each with a DEFAULT clause"
stmts = ["CREATE TABLE t1 (c1 INTEGER DEFAULT 12345, "
"c2 NUMERIC DEFAULT 98.76, c3 REAL DEFAULT 15e-2, "
"c4 TEXT DEFAULT 'Abc def', c5 DATE DEFAULT CURRENT_DATE, "
"c6 TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, "
"c7 BOOLEAN DEFAULT FALSE)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [
{'c1': {'type': 'integer', 'default': '12345'}},
{'c2': {'type': 'numeric', 'default': '98.76'}},
{'c3': {'type': 'real', 'default': '0.15'}},
{'c4': {'type': 'text', 'default': "'Abc def'::text"}},
{'c5': {'type': 'date', 'default': "CURRENT_DATE"}},
{'c6': {'type': 'timestamp with time zone', 'default':
"CURRENT_TIMESTAMP"}},
{'c7': {'type': 'boolean', 'default': 'false'}}]}
assert dbmap['schema sd']['table t1'] == expmap
def test_statistics(self):
"Map a table with column statistics"
stmts = [CREATE_STMT1, "ALTER TABLE t1 ALTER c1 SET STATISTICS 100"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer', 'statistics': 100}},
{'c2': {'type': 'text'}}]}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_identity(self):
"Map a table with an IDENTITY column"
stmts = ["CREATE TABLE t1 (c1 integer GENERATED ALWAYS AS IDENTITY, "
"c2 text)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer', 'not_null': True,
'identity': 'always'}},
{'c2': {'type': 'text'}}]}
assert dbmap['schema sd']['table t1'] == expmap
expmap = {'data_type': 'integer', 'start_value': 1, 'increment_by': 1,
'max_value': 2147483647, 'min_value': None, 'cache_value': 1,
'owner_table': 't1', 'owner_column': 'c1'}
assert dbmap['schema sd']['sequence t1_c1_seq'] == expmap
class ColumnToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of column-related statements from input schemas"""
def test_create_table_with_defaults(self):
"Create a table with two column DEFAULTs, one referring to a SEQUENCE"
inmap = self.std_map()
inmap.update({'schema s1': {'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True,
'default':
"nextval('s1.t1_c1_seq'::regclass)"}},
{'c2': {'type': 'text', 'not_null': True,
'collation': 'en_US.utf8'}},
{'c3': {'type': 'date', 'not_null': True,
'default': "('now'::text)::date"}}]},
'sequence t1_c1_seq': {
'cache_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'start_value': 1,
'owner_table': 't1', 'owner_column': 'c1'}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE SEQUENCE s1.t1_c1_seq " \
"START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1"
assert fix_indent(sql[1]) == "CREATE TABLE s1.t1 (c1 integer " \
"NOT NULL DEFAULT nextval('s1.t1_c1_seq'::regclass), c2 text " \
'NOT NULL COLLATE "en_US.utf8", c3 date NOT NULL ' \
"DEFAULT ('now'::text)::date)"
assert sql[2] == "ALTER SEQUENCE s1.t1_c1_seq OWNED BY s1.t1.c1"
def test_create_column_identity(self):
"Create a table with a column GENERATED AS IDENTITY"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True,
'identity': 'by default'}},
{'c2': {'type': 'text'}}]},
'sequence t1_c1_seq': {
'cache_value': 1, 'data_type': 'integer', 'increment_by': 1,
'max_value': None, 'min_value': None, 'start_value': 1,
'owner_table': 't1', 'owner_column': 'c1'}})
sql = self.to_sql(inmap)
assert len(sql) == 1
assert fix_indent(sql[0]) == (
"CREATE TABLE sd.t1 (c1 integer NOT NULL GENERATED BY DEFAULT AS "
"IDENTITY (SEQUENCE NAME sd.t1_c1_seq START WITH 1 INCREMENT BY 1 "
"NO MINVALUE NO MAXVALUE CACHE 1), c2 text)")
def test_change_column_default(self):
"Change the default value for an existing column"
stmt = "CREATE TABLE t1 (c1 integer, c2 boolean default true)"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'boolean', 'default': 'false'}}]}})
sql = self.to_sql(inmap, [stmt])
assert fix_indent(sql[0]) == \
"ALTER TABLE sd.t1 ALTER COLUMN c2 SET DEFAULT false"
def test_set_column_not_null(self):
"Change a nullable column to NOT NULL"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT1])
assert fix_indent(sql[0]) == \
"ALTER TABLE sd.t1 ALTER COLUMN c1 SET NOT NULL"
def test_change_column_types(self):
"Change the datatypes of two columns"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'bigint'}},
{'c2': {'type': 'varchar(25)'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT1])
assert fix_indent(sql[0]) == \
"ALTER TABLE sd.t1 ALTER COLUMN c1 TYPE bigint"
assert fix_indent(sql[1]) == \
"ALTER TABLE sd.t1 ALTER COLUMN c2 TYPE varchar(25)"
def test_add_column1(self):
"Add new column to a table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}, {'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT2])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c4 text"
def test_add_column2(self):
"Add column to a table that has a dropped column"
stmts = [CREATE_STMT2, "ALTER TABLE t1 DROP COLUMN c2"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}]}})
sql = self.to_sql(inmap, stmts)
assert len(sql) == 1
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c2 text"
def test_add_column3(self):
"No change on a table that has a dropped column"
stmts = [CREATE_STMT3, DROP_COL_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, stmts)
assert len(sql) == 0
def test_add_column4(self):
"Add two columns to a table that has a dropped column"
stmts = [CREATE_STMT2, DROP_COL_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}, {'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c3 date"
assert fix_indent(sql[1]) == "ALTER TABLE sd.t1 ADD COLUMN c4 text"
def test_drop_column1(self):
"Drop a column from the end of a table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT3])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 DROP COLUMN c4"
def test_drop_column2(self):
"Drop a column from the middle of a table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT3])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 DROP COLUMN c3"
def test_drop_column3(self):
"Drop a column from the beginning of a table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c2': {'type': 'text'}}, {'c3': {'type': 'date'}},
{'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT3])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 DROP COLUMN c1"
def test_rename_column(self):
"Rename a table column"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c3': {'type': 'text', 'oldname': 'c2'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT1])
assert sql[0] == "ALTER TABLE sd.t1 RENAME COLUMN c2 TO c3"
def test_drop_add_column1(self):
"Drop and re-add table column from the end, almost like a RENAME"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c4': {'type': 'date'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT2])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c4 date"
assert sql[1] == "ALTER TABLE sd.t1 DROP COLUMN c3"
def test_drop_add_column2(self):
"Drop and re-add table column from the beginning"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c2': {'type': 'text'}}, {'c3': {'type': 'date'}},
{'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT2])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c4 text"
assert sql[1] == "ALTER TABLE sd.t1 DROP COLUMN c1"
def test_drop_add_column3(self):
"Drop and re-add table columns from table with dropped column"
stmts = [CREATE_STMT2, DROP_COL_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c2': {'type': 'text'}}, {'c3': {'type': 'date'}},
{'c4': {'type': 'text'}}]}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c3 date"
assert fix_indent(sql[1]) == "ALTER TABLE sd.t1 ADD COLUMN c4 text"
assert sql[2] == "ALTER TABLE sd.t1 DROP COLUMN c1"
def test_drop_column_in_schema(self):
"Drop a column from a table in a non-default schema"
stmts = ["CREATE SCHEMA s1",
"CREATE TABLE s1.t1 (c1 integer, c2 text, c3 date)"]
inmap = self.std_map()
inmap.update({'schema s1': {'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == "ALTER TABLE s1.t1 DROP COLUMN c3"
def test_inherit_add_parent_column(self):
"Add a column to parent table, child should not add as well"
stmts = [CREATE_STMT1, "CREATE TABLE t2 (c3 date) INHERITS (t1)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c4': {'type': 'text'}}]}})
inmap['schema sd'].update({'table t2': {
'columns': [{'c1': {'type': 'integer', 'inherited': True}},
{'c2': {'type': 'text', 'inherited': True}},
{'c3': {'type': 'date'}},
{'c4': {'type': 'text', 'inherited': True}}],
'inherits': ['t1']}})
sql = self.to_sql(inmap, stmts)
assert len(sql) == 1
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c4 text"
def test_inherit_drop_parent_column(self):
"Drop a column from a parent table, child should not drop as well"
stmts = [CREATE_STMT1, "CREATE TABLE t2 (c3 date) INHERITS (t1)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}]}})
inmap['schema sd'].update({'table t2': {
'columns': [{'c1': {'type': 'integer', 'inherited': True}},
{'c3': {'type': 'date'}}], 'inherits': ['t1']}})
sql = self.to_sql(inmap, stmts)
assert len(sql) == 1
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 DROP COLUMN c2"
def test_alter_statistics(self):
"Alter a table to add column statistics"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'statistics': 100}},
{'c2': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT1, "ALTER TABLE t1 ALTER c2 "
"SET STATISTICS 1000"])
assert fix_indent(sql[0]) == \
"ALTER TABLE sd.t1 ALTER COLUMN c1 SET STATISTICS 100"
assert fix_indent(sql[1]) == \
"ALTER TABLE sd.t1 ALTER COLUMN c2 SET STATISTICS -1"
File diff suppressed because it is too large Load Diff
+109
View File
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
"""Test conversions"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE CONVERSION sd.c1 FOR 'LATIN1' TO 'UTF8' " \
"FROM iso8859_1_to_utf8"
DROP_STMT = "DROP CONVERSION IF EXISTS sd.c1"
COMMENT_STMT = "COMMENT ON CONVERSION sd.c1 IS 'Test conversion c1'"
class ConversionToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing conversions"""
def test_map_conversion1(self):
"Map a conversion"
dbmap = self.to_map([CREATE_STMT])
expmap = {'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}
assert dbmap['schema sd']['conversion c1'] == expmap
def test_map_conversion_comment(self):
"Map a conversion comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['conversion c1']['description'] == \
'Test conversion c1'
class ConversionToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input conversions"""
def test_create_conversion(self):
"Create a conversion"
inmap = self.std_map()
inmap['schema sd'].update({'conversion c1': {
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
def test_create_conversion_schema(self):
"Create a conversion in a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'conversion c1': {
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8', 'default': True}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE DEFAULT CONVERSION s1.c1 " \
"FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8"
def test_bad_conversion_map(self):
"Error creating a conversion with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'c1': {
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_conversion(self):
"Drop an existing conversion"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql[0] == "DROP CONVERSION sd.c1"
def test_conversion_with_comment(self):
"Create a conversion with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'conversion c1': {
'description': 'Test conversion c1',
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_comment_on_conversion(self):
"Create a comment for an existing conversion"
inmap = self.std_map()
inmap['schema sd'].update({'conversion c1': {
'description': 'Test conversion c1',
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == [COMMENT_STMT]
def test_drop_conversion_comment(self):
"Drop a comment on an existing conversion"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'conversion c1': {
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON CONVERSION sd.c1 IS NULL"]
def test_change_conversion_comment(self):
"Change existing comment on a conversion"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'conversion c1': {
'description': 'Changed conversion c1',
'source_encoding': 'LATIN1', 'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8'}})
sql = self.to_sql(inmap, stmts)
assert sql == [
"COMMENT ON CONVERSION sd.c1 IS 'Changed conversion c1'"]
+119
View File
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
"""Test domains"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATEFUNC_STMT = ("CREATE FUNCTION sd.dc1(integer) RETURNS bool LANGUAGE sql "
"IMMUTABLE AS $_$select true$_$")
CREATE_STMT = "CREATE DOMAIN sd.d1 AS integer"
DROP_STMT = "DROP DOMAIN IF EXISTS d1"
COMMENT_STMT = "COMMENT ON DOMAIN d1 IS 'Test domain d1'"
class DomainToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created domains"""
def test_domain(self):
"Map a simple domain"
dbmap = self.to_map([CREATE_STMT])
assert dbmap['schema sd']['domain d1'] == {'type': 'integer'}
def test_domain_not_null(self):
"Map a domain with a NOT NULL constraint"
dbmap = self.to_map([CREATE_STMT + " NOT NULL"])
expmap = {'type': 'integer', 'not_null': True}
assert dbmap['schema sd']['domain d1'] == expmap
def test_domain_default(self):
"Map a domain with a DEFAULT"
dbmap = self.to_map(["CREATE DOMAIN d1 AS date DEFAULT CURRENT_DATE"])
expmap = {'type': 'date', 'default': 'CURRENT_DATE'}
assert dbmap['schema sd']['domain d1'] == expmap
def test_domain_check(self):
"Map a domain with a CHECK constraint"
dbmap = self.to_map([CREATE_STMT + " CHECK (VALUE >= 1888)"])
expmap = {'type': 'integer', 'check_constraints': {
'd1_check': {'expression': '(VALUE >= 1888)'}}}
assert dbmap['schema sd']['domain d1'] == expmap
def test_domain_depend_function(self):
"A domain is created after a function it depends on"
dbmap = self.to_map([CREATEFUNC_STMT,
CREATE_STMT + " CHECK (dc1(VALUE))"])
expmap = {'type': 'integer',
'check_constraints': {
'd1_check': {
'expression': 'sd.dc1(VALUE)',
'depends_on': ['function dc1(integer)']}}}
assert dbmap['schema sd']['domain d1'] == expmap
class DomainToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input domains"""
def test_create_domain_simple(self):
"Create a simple domain"
inmap = self.std_map()
inmap['schema sd'].update({'domain d1': {'type': 'integer'}})
sql = self.to_sql(inmap)
assert sql == [CREATE_STMT]
def test_create_domain_default(self):
"Create a domain with a DEFAULT and NOT NULL"
inmap = self.std_map()
inmap['schema sd'].update({'domain d1': {
'type': 'integer', 'not_null': True, 'default': 0}})
sql = self.to_sql(inmap)
assert sql == [CREATE_STMT + " NOT NULL DEFAULT 0"]
def test_create_domain_check(self):
"Create a domain with a CHECK constraint"
inmap = self.std_map()
inmap['schema sd'].update({'domain d1': {
'type': 'integer', 'check_constraints': {'d1_check': {
'expression': '(VALUE >= 1888)'}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert fix_indent(sql[1]) == "ALTER DOMAIN sd.d1 ADD CONSTRAINT " + \
"d1_check CHECK (VALUE >= 1888)"
def test_add_domain_check(self):
"Add a CHECK constraint to a domain"
inmap = self.std_map()
inmap['schema sd'].update({'domain d1': {
'type': 'integer', 'check_constraints': {'d1_check': {
'expression': '(VALUE >= 1888)'}}}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert fix_indent(sql[0]) == "ALTER DOMAIN sd.d1 " + \
"ADD CONSTRAINT d1_check CHECK (VALUE >= 1888)"
def test_drop_domain(self):
"Drop an existing domain"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql == ["DROP DOMAIN sd.d1"]
def test_rename_domain(self):
"Rename an existing domain"
inmap = self.std_map()
inmap['schema sd'].update({'domain d2': {
'oldname': 'd1', 'type': 'integer'}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == ["ALTER DOMAIN sd.d1 RENAME TO d2"]
def test_create_domain_before_function(self):
"Check that the domain is created after a function it depends on"
inmap = self.std_map()
inmap['schema sd'].update({'domain d1': {
'type': 'integer', 'check_constraints': {
'd1_check': {'expression': 'dc1(VALUE)',
'depends_on': ['function dc1(integer)']}}},
'function dc1(integer)': {
'language': 'sql', 'returns': 'bool',
'source': 'select true', 'volatility': 'immutable'}})
sql = self.to_sql(inmap)
assert len(sql) == 4
assert sql[1] == CREATE_STMT
assert fix_indent(sql[2]) == CREATEFUNC_STMT
assert sql[3].startswith("ALTER DOMAIN sd.d1 ADD CONSTRAINT")
+120
View File
@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
"""Test event triggers"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
FUNC_SRC = "BEGIN RAISE NOTICE 'Command % executed', tg_tag; END"
CREATE_FUNC_STMT = "CREATE FUNCTION sd.f1() RETURNS event_trigger " \
"LANGUAGE plpgsql AS $_$%s$_$" % FUNC_SRC
CREATE_STMT = "CREATE EVENT TRIGGER et1 ON ddl_command_end %s" \
"EXECUTE PROCEDURE sd.f1()"
DROP_TABLE_STMT = "DROP TABLE IF EXISTS t1"
DROP_FUNC_STMT = "DROP FUNCTION IF EXISTS f1()"
COMMENT_STMT = "COMMENT ON EVENT TRIGGER et1 IS 'Test event trigger et1'"
class EventTriggerToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing event triggers"""
def test_map_event_trigger_simple(self):
"Map a simple event trigger"
stmts = [CREATE_FUNC_STMT, CREATE_STMT % '']
dbmap = self.to_map(stmts)
assert dbmap['event trigger et1'] == {
'enabled': True, 'event': 'ddl_command_end',
'procedure': 'sd.f1()'}
def test_map_event_trigger_filter(self):
"Map a trigger with tag filter variables"
stmts = [CREATE_FUNC_STMT, CREATE_STMT % (
"WHEN tag IN ('CREATE TABLE', 'CREATE VIEW') ")]
dbmap = self.to_map(stmts)
assert dbmap['event trigger et1'] == {
'enabled': True, 'event': 'ddl_command_end',
'tags': ['CREATE TABLE', 'CREATE VIEW'], 'procedure': 'sd.f1()'}
def test_map_event_trigger_comment(self):
"Map a trigger comment"
stmts = [CREATE_FUNC_STMT, CREATE_STMT % '', COMMENT_STMT]
dbmap = self.to_map(stmts)
assert dbmap['event trigger et1']['description'] == \
'Test event trigger et1'
class EventTriggerToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input triggers"""
def test_create_event_trigger_simple(self):
"Create a simple event trigger"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'event_trigger',
'source': FUNC_SRC}})
inmap.update({'event trigger et1': {
'enabled': True, 'event': 'ddl_command_end',
'procedure': 'sd.f1()'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_FUNC_STMT
assert fix_indent(sql[1]) == CREATE_STMT % ''
def test_create_event_trigger_filter(self):
"Create an event trigger with tag filter variables"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'event_trigger',
'source': FUNC_SRC}})
inmap.update({'event trigger et1': {
'enabled': True, 'event': 'ddl_command_end',
'procedure': 'sd.f1()', 'tags': ['CREATE TABLE', 'CREATE VIEW']}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_FUNC_STMT
assert fix_indent(sql[1]) == CREATE_STMT % (
"WHEN tag IN ('CREATE TABLE', 'CREATE VIEW') ")
def test_create_event_trigger_func_schema(self):
"Create an event trigger with function in a non-default schema"
inmap = self.std_map(plpgsql_installed=True)
inmap.update({'schema s1': {'function f1()': {
'language': 'plpgsql', 'returns': 'event_trigger',
'source': FUNC_SRC}}})
inmap.update({'event trigger et1': {
'enabled': True, 'event': 'ddl_command_end',
'procedure': 's1.f1()'}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE FUNCTION s1.f1() " \
"RETURNS event_trigger LANGUAGE plpgsql AS $_$%s$_$" % FUNC_SRC
assert fix_indent(sql[1]) == "CREATE EVENT TRIGGER et1 " \
"ON ddl_command_end EXECUTE PROCEDURE s1.f1()"
def test_drop_event_trigger(self):
"Drop an existing event trigger"
stmts = [CREATE_FUNC_STMT, CREATE_STMT % '']
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'event_trigger',
'source': FUNC_SRC}})
sql = self.to_sql(inmap, stmts)
assert sql == ["DROP EVENT TRIGGER et1"]
def test_drop_event_trigger_function(self):
"Drop an existing event trigger and the related function"
stmts = [CREATE_FUNC_STMT, CREATE_STMT % '']
inmap = self.std_map(plpgsql_installed=True)
sql = self.to_sql(inmap, stmts)
assert sql[0] == "DROP EVENT TRIGGER et1"
assert sql[1] == "DROP FUNCTION sd.f1()"
def test_create_event_trigger_with_comment(self):
"Create an event trigger with a comment"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'event_trigger',
'source': FUNC_SRC}})
inmap.update({'event trigger et1': {
'enabled': True, 'event': 'ddl_command_end',
'procedure': 'sd.f1()', 'description': 'Test event trigger et1'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_FUNC_STMT
assert fix_indent(sql[1]) == CREATE_STMT % ''
assert sql[2] == COMMENT_STMT
+158
View File
@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
"""Test extensions"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE EXTENSION pg_trgm"
TRGM_COMMENT = "text similarity measurement and index searching based on " \
"trigrams"
PLR_DESCR = "load R interpreter and execute R script from within a database"
class ExtensionToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing extensions"""
superuser = True
def base_version(self):
if self.db.version < 110000:
return '1.3'
elif self.db.version < 120000:
return '1.4'
elif self.db.version < 150000:
return '1.5'
return '1.6'
def test_map_extension(self):
"Map an existing extension"
VERS = self.base_version()
dbmap = self.to_map([CREATE_STMT])
assert dbmap['extension pg_trgm'] == {
'schema': 'sd', 'version': VERS, 'description': TRGM_COMMENT}
def test_map_no_depends(self):
"Ensure no dependencies are included when mapping an extension"
dbmap = self.to_map([CREATE_STMT])
assert 'type gtrgm' not in dbmap['schema sd']
assert 'operator %(text, text)' not in dbmap['schema sd']
assert 'function show_trgm(text)' not in dbmap['schema sd']
def test_map_lang_extension(self):
"Map a procedural language as an extension"
dbmap = self.to_map(["CREATE EXTENSION plperl"])
assert dbmap['extension plperl'] == {
'schema': 'pg_catalog', 'version': '1.0',
'description': "PL/Perl procedural language"}
assert 'language plperl' not in dbmap
def test_map_extern_lang_extension(self):
"Map an externally maintained procedural language"
if self.db.version < 150000:
self.skipTest('Only available since PG 15')
dbmap = self.to_map(["CREATE EXTENSION plr"])
assert dbmap['extension plr'] == {
'schema': 'sd', 'version': '8.4.5', 'description': PLR_DESCR}
assert dbmap['language plr'] == {'trusted': False}
# Ensure we don't map core-maintained language
assert 'language plpgsql' not in dbmap
def test_map_extension_schema(self):
"Map an existing extension"
VERS = self.base_version()
dbmap = self.to_map(["CREATE SCHEMA s1", CREATE_STMT + " SCHEMA s1"])
assert dbmap['extension pg_trgm'] == {
'schema': 's1', 'version': VERS, 'description': TRGM_COMMENT}
def test_map_extension_plpython3u(self):
"Test a function created with extension other than plpgsql/plperl"
# See issue #103
dbmap = self.to_map(["CREATE EXTENSION plpython3u",
"CREATE FUNCTION test() RETURNS int AS "
"'return 1' LANGUAGE plpython3u"])
assert 'extension plpython3u' in dbmap
class ExtensionToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input extensions"""
def test_create_extension_simple(self):
"Create a extension that didn't exist"
inmap = self.std_map()
inmap.update({'extension pg_trgm': {'schema': 'sd'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT + " SCHEMA sd"
def test_bad_extension_map(self):
"Error creating a extension with a bad map"
inmap = self.std_map()
inmap.update({'pg_trgm': {'schema': 'sd'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_extension(self):
"Drop an existing extension"
sql = self.to_sql(self.std_map(), [CREATE_STMT], superuser=True)
assert sql == ["DROP EXTENSION pg_trgm"]
def test_create_extension_schema(self):
"Create a extension in a given schema"
inmap = self.std_map()
inmap.update({'schema s1': {},
'extension pg_trgm': {'schema': 's1', 'version': '1.0'}})
sql = self.to_sql(inmap)
assert sql[0] == 'CREATE SCHEMA s1'
assert fix_indent(sql[1]) == \
"CREATE EXTENSION pg_trgm SCHEMA s1 VERSION '1.0'"
def test_create_lang_extension(self):
"Create a language extension and a function in that language"
inmap = self.std_map()
inmap.update({'extension plperl': {'schema': 'pg_catalog',
'description':
"PL/Perl procedural language"}})
inmap['schema sd'].update({'function f1()': {
'language': 'plperl', 'returns': 'text',
'source': "return \"dummy\";"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE EXTENSION plperl"
# skip over COMMENT statement
assert fix_indent(sql[2]) == "CREATE FUNCTION sd.f1() RETURNS text " \
"LANGUAGE plperl AS $_$return \"dummy\";$_$"
def test_create_extern_lang_extension(self):
"Create an externally maintained language together with a function"
inmap = self.std_map()
inmap.update({'extension plr': {'schema': 'sd',
'description': PLR_DESCR}})
inmap.update({'language plr': { 'trusted': 'false'}})
inmap['schema sd'].update({'function f1()': {
'language': 'plr', 'returns': 'text',
'source': "return \"dummy\";"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE EXTENSION plr SCHEMA sd"
# skip over COMMENT statement
assert fix_indent(sql[2]) == "CREATE FUNCTION sd.f1() RETURNS text " \
"LANGUAGE plr AS $_$return \"dummy\";$_$"
def test_comment_extension(self):
"Change the comment for an existing extension"
inmap = self.std_map()
inmap.update({'extension pg_trgm': {
'schema': 'sd', 'description': "Trigram extension"}})
sql = self.to_sql(inmap, [CREATE_STMT], superuser=True)
assert sql == ["COMMENT ON EXTENSION pg_trgm IS 'Trigram extension'"]
def test_no_alter_owner_extension(self):
"""Do not alter the owner of an existing extension.
ALTER EXTENSION extension_name OWNER is not a valid form.
"""
# create a new owner that is different from self.db.user
new_owner = 'new_%s' % self.db.user
inmap = self.std_map()
inmap.update({'extension pg_trgm': {'schema': 'sd',
'owner': new_owner}})
sql = self.to_sql(inmap, [CREATE_STMT], superuser=True)
assert 'ALTER EXTENSION pg_trgm OWNER TO %s' % new_owner not in sql
+268
View File
@@ -0,0 +1,268 @@
# -*- coding: utf-8 -*-
"""Test external files used in --multiple-files option"""
import sys
import pytest
from pyrseas.testutils import PyrseasTestCase
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.dbobject.schema import Schema
from pyrseas.dbobject.function import Function
from pyrseas.dbobject.table import Sequence, Table
from pyrseas.dbobject.view import View
if sys.platform == 'win32':
COLL = 'French_France.1252'
else:
COLL = 'fr_FR.UTF-8'
CREATE_FDW = "CREATE FOREIGN DATA WRAPPER "
SOURCE1 = "SELECT 'dummy'::text"
SOURCE2 = "SELECT $1::text"
DROP_TSC = "DROP TEXT SEARCH CONFIGURATION IF EXISTS tsc1, tsc2"
DROP_TSP = "DROP TEXT SEARCH PARSER IF EXISTS tsp1 CASCADE"
class ExternalFilenameMapTestCase(DatabaseToMapTestCase):
def setUp(self):
super(ExternalFilenameMapTestCase, self).setUp()
self.remove_tempfiles()
def test_map_casts(self):
"Map casts"
self.to_map(["CREATE FUNCTION int2_bool(smallint) RETURNS boolean "
"LANGUAGE sql IMMUTABLE AS "
"$_$SELECT CAST($1::int AS boolean)$_$",
"CREATE DOMAIN d1 AS integer",
"CREATE CAST (smallint AS boolean) WITH FUNCTION "
"int2_bool(smallint)",
"CREATE CAST (d1 AS integer) WITH INOUT AS IMPLICIT"],
superuser=True, multiple_files=True)
expmap = {'cast (smallint as boolean)': {
'function': 'sd.int2_bool(smallint)', 'context': 'explicit',
'method': 'function'}, 'cast (sd.d1 as integer)':
{'context': 'implicit', 'method': 'inout',
'depends_on': ['domain d1']}}
assert self.yaml_load('cast.yaml') == expmap
def test_map_extension(self):
"Map extensions"
TRGM_VERS = '1.6'
if self.db.version < 110000:
TRGM_VERS = '1.3'
elif self.db.version < 130000:
TRGM_VERS = '1.4'
elif self.db.version < 150000:
TRGM_VERS = '1.5'
self.to_map(["CREATE EXTENSION pg_trgm"], superuser=True,
multiple_files=True)
expmap = {'extension plpgsql': {
'schema': 'pg_catalog', 'version': '1.0',
'description': 'PL/pgSQL procedural language'},
'extension pg_trgm': {'schema': 'sd', 'version': TRGM_VERS,
'description': "text similarity measurement "
"and index searching based on trigrams"}}
assert self.yaml_load('extension.yaml') == expmap
def test_map_fd_wrappers(self):
"Map foreign data wrappers"
self.to_map([CREATE_FDW + "fdw1", CREATE_FDW + "fdw2 "
"OPTIONS (debug 'true')"], superuser=True,
multiple_files=True)
expmap = {'foreign data wrapper fdw1': {},
'foreign data wrapper fdw2': {'options': ['debug=true']}}
assert self.yaml_load('foreign_data_wrapper.yaml') == expmap
def test_collations(self):
"Map collations"
self.to_map(["CREATE COLLATION coll1 (LC_COLLATE = '%s', "
"LC_CTYPE = '%s')" % (COLL, COLL),
"COMMENT ON COLLATION coll1 IS 'A test collation'",
"CREATE COLLATION coll2 (LC_COLLATE = '%s', "
"LC_CTYPE = '%s')" % (COLL, COLL)],
multiple_files=True)
assert self.yaml_load('collation.yaml', 'schema.sd') == {
'collation coll1': {'lc_collate': COLL, 'lc_ctype': COLL,
'description': "A test collation"},
'collation coll2': {'lc_collate': COLL, 'lc_ctype': COLL}}
def test_map_conversion(self):
"Map conversions"
self.to_map(["CREATE CONVERSION conv1 FOR 'LATIN1' TO 'UTF8' "
"FROM iso8859_1_to_utf8",
"COMMENT ON CONVERSION conv1 IS 'A test conversion'"],
multiple_files=True)
assert self.yaml_load('conversion.yaml', 'schema.sd') == {
'conversion conv1': {'source_encoding': 'LATIN1',
'dest_encoding': 'UTF8',
'function': 'iso8859_1_to_utf8',
'description': 'A test conversion'}}
def test_map_functions(self):
"Map functions"
self.to_map(["CREATE FUNCTION f1() RETURNS text LANGUAGE sql "
"IMMUTABLE AS $_$%s$_$" % SOURCE1,
"CREATE FUNCTION f2() RETURNS text LANGUAGE sql "
"IMMUTABLE AS $_$%s$_$" % SOURCE1], multiple_files=True)
expmap = {'language': 'sql', 'returns': 'text',
'source': SOURCE1, 'volatility': 'immutable'}
assert self.yaml_load('function.f1.yaml', 'schema.sd') == {
'function f1()': expmap}
assert self.yaml_load('function.f2.yaml', 'schema.sd') == {
'function f2()': expmap}
def test_map_functions_merged(self):
"Map functions into a merged file"
self.to_map(["CREATE FUNCTION f3(integer) RETURNS text LANGUAGE sql "
"IMMUTABLE AS $_$%s$_$" % SOURCE2,
"CREATE FUNCTION f3(real) RETURNS text LANGUAGE sql "
"IMMUTABLE AS $_$%s$_$" % SOURCE2], multiple_files=True)
expmap = {'language': 'sql', 'returns': 'text',
'source': SOURCE2, 'volatility': 'immutable'}
assert self.yaml_load('function.f3.yaml', 'schema.sd') == {
'function f3(integer)': expmap, 'function f3(real)': expmap}
def test_map_operator(self):
"Map operators and an operator class"
self.to_map(["CREATE OPERATOR < (PROCEDURE = int4lt, LEFTARG = int, "
"RIGHTARG = int)", "CREATE OPERATOR = (PROCEDURE = "
"int4eq, LEFTARG = int, RIGHTARG = int)", "CREATE "
"OPERATOR > (PROCEDURE = int4gt, LEFTARG = int, "
"RIGHTARG = int)",
"CREATE OPERATOR CLASS oc1 FOR TYPE integer USING btree "
"AS OPERATOR 1 sd.<, OPERATOR 3 sd.=, OPERATOR "
"5 sd.>, FUNCTION 1 btint4cmp(integer,integer)"],
superuser=True, multiple_files=True)
oprmap = {'operator <(integer, integer)': {'procedure': 'int4lt'},
'operator =(integer, integer)': {'procedure': 'int4eq'},
'operator >(integer, integer)': {'procedure': 'int4gt'}}
opcmap = {'operator class oc1 using btree': {
'type': 'integer', 'operators': {
1: 'sd.<(integer,integer)', 3: 'sd.=(integer,integer)',
5: 'sd.>(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}}}
assert self.yaml_load('operator.yaml', 'schema.sd') == oprmap
assert self.yaml_load('operator_class.yaml', 'schema.sd') == opcmap
def test_map_operator_family(self):
"Map operator families"
self.to_map(["CREATE SCHEMA s1",
"CREATE OPERATOR FAMILY s1.of1 USING btree",
"CREATE OPERATOR FAMILY s1.of2 USING btree"],
superuser=True, multiple_files=True)
assert self.yaml_load('operator_family.yaml', 'schema.s1') == {
'operator family of1 using btree': {},
'operator family of2 using btree': {}}
def test_map_tables(self):
"Map tables"
self.to_map(["CREATE TABLE t1 (c1 integer PRIMARY KEY, c2 text)",
"CREATE TABLE t2 (c1 integer, c2 integer, c3 text, "
"FOREIGN KEY (c2) REFERENCES t1 (c1))"],
multiple_files=True)
expmap1 = {'table t1': {'columns': [
{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'text'}}],
'primary_key': {'t1_pkey': {'columns': ['c1']}}}}
expmap2 = {'table t2': {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'integer'}},
{'c3': {'type': 'text'}}],
'foreign_keys': {'t2_c2_fkey': {'columns': ['c2'], 'references': {
'schema': 'sd', 'table': 't1', 'columns': ['c1']}}}}}
assert self.yaml_load('table.t1.yaml', 'schema.sd') == expmap1
assert self.yaml_load('table.t2.yaml', 'schema.sd') == expmap2
def test_map_tables_merged(self):
"Map tables into a merged file"
self.to_map(["CREATE TABLE account_transfers_with_extra_padding_1 "
"(c1 integer, c2 text)",
"CREATE TABLE account_transfers_with_extra_padding_2 "
"(c1 integer, c2 text)"],
multiple_files=True)
expmap = {'table account_transfers_with_extra_padding_1': {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}]},
'table account_transfers_with_extra_padding_2': {'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}]}}
assert self.yaml_load('table.account_transfers_with_extra_pad.yaml',
'schema.sd') == expmap
def test_map_textsearch(self):
"Map text search components"
self.to_map([DROP_TSC, DROP_TSP,
"CREATE TEXT SEARCH PARSER tsp1 (START = prsd_start, "
"GETTOKEN = prsd_nexttoken, END = prsd_end, "
"LEXTYPES = prsd_lextype, HEADLINE = prsd_headline)",
"CREATE TEXT SEARCH CONFIGURATION tsc1 (PARSER = tsp1)",
"CREATE TEXT SEARCH CONFIGURATION tsc2 (PARSER = tsp1)"],
superuser=True, multiple_files=True)
assert self.yaml_load('text_search_configuration.yaml',
'schema.sd') == \
{'text search configuration tsc1': {'parser': 'tsp1'},
'text search configuration tsc2': {'parser': 'tsp1'}}
assert self.yaml_load('text_search_parser.yaml', 'schema.sd') == {
'text search parser tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype',
'headline': 'prsd_headline'}}
self.db.execute(DROP_TSC)
self.db.execute_commit(DROP_TSP)
def test_map_drop_table(self):
"Map three tables, drop one and map the remaining two"
self.to_map(["CREATE TABLE t1 (c1 integer, c2 text)",
"CREATE TABLE t2 (c1 integer, c2 text)",
"CREATE TABLE t3 (c1 integer, c2 text)"],
multiple_files=True)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}
for tbl in ['t1', 't2', 't3']:
assert self.yaml_load('table.%s.yaml' % tbl, 'schema.sd')[
'table %s' % tbl] == expmap
self.to_map(["DROP TABLE t2"], multiple_files=True)
with pytest.raises(IOError) as exc:
self.yaml_load('table.t2.yaml', 'schema.sd')
assert 'No such file' in str(exc.value)
for tbl in ['t1', 't3']:
assert self.yaml_load('table.%s.yaml' % tbl, 'schema.sd')[
'table %s' % tbl] == expmap
class ExternalFilenameTestCase(PyrseasTestCase):
def test_function(self):
"Map a function"
obj = Function("Weird/Or-what?", 'sd', '', None, [], None, None,
None, None)
assert obj.extern_filename() == 'function.weird_or_what_.yaml'
def test_schema(self):
"Map a schema"
obj = Schema(name="A/C Schema")
assert obj.extern_filename() == 'schema.a_c_schema.yaml'
def test_long_name_schema(self):
"Map a schema with a long name"
nm = 'a_schema_with_a_very_but_very_very_long_long_long_loooonng_name'
obj = Schema(name=nm)
assert obj.extern_filename() == 'schema.%s.yaml' % nm
def test_table(self):
"Map a table"
obj = Table("Weird/Or-what?HOW.WeiRD", '', None, None, [])
assert obj.extern_filename() == 'table.weird_or_what_how_weird.yaml'
def test_table_unicode(self):
"Map a table with Unicode characters"
obj = Table("Fundação\\Größe таблица", '', None, None, [])
assert obj.extern_filename() == 'table.fundação_größe_таблица.yaml'
def test_sequence(self):
"Map a sequence"
obj = Sequence("Weird/Or-what?_seq", '', None, None, [])
assert obj.extern_filename() == 'sequence.weird_or_what__seq.yaml'
def test_view(self):
"Map a view"
obj = View("Weirder/Don't You Think?", '', None, None, [], '')
assert obj.extern_filename() == 'view.weirder_don_t_you_think_.yaml'
+395
View File
@@ -0,0 +1,395 @@
# -*- coding: utf-8 -*-
"""Test text search objects"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_FDW_STMT = "CREATE FOREIGN DATA WRAPPER fdw1"
CREATE_FS_STMT = "CREATE SERVER fs1 FOREIGN DATA WRAPPER fdw1"
CREATE_UM_STMT = "CREATE USER MAPPING FOR PUBLIC SERVER fs1"
CREATE_FT_STMT = "CREATE FOREIGN TABLE sd.ft1 (c1 integer, c2 text) SERVER fs1"
DROP_FDW_STMT = "DROP FOREIGN DATA WRAPPER IF EXISTS fdw1"
DROP_FS_STMT = "DROP SERVER IF EXISTS fs1"
DROP_UM_STMT = "DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER fs1"
COMMENT_FDW_STMT = "COMMENT ON FOREIGN DATA WRAPPER fdw1 IS " \
"'Test foreign data wrapper fdw1'"
COMMENT_FS_STMT = "COMMENT ON SERVER fs1 IS 'Test server fs1'"
class ForeignDataWrapperToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing foreign data wrappers"""
superuser = True
def test_map_fd_wrapper(self):
"Map an existing foreign data wrapper"
dbmap = self.to_map([CREATE_FDW_STMT])
assert dbmap['foreign data wrapper fdw1'] == {}
def test_map_wrapper_validator(self):
"Map a foreign data wrapper with a validator function"
dbmap = self.to_map(["CREATE FOREIGN DATA WRAPPER fdw1 "
"VALIDATOR postgresql_fdw_validator"])
assert dbmap['foreign data wrapper fdw1'] == {
'validator': 'postgresql_fdw_validator'}
def test_map_wrapper_options(self):
"Map a foreign data wrapper with options"
dbmap = self.to_map(["CREATE FOREIGN DATA WRAPPER fdw1 "
"OPTIONS (debug 'true')"])
assert dbmap['foreign data wrapper fdw1'] == {
'options': ['debug=true']}
def test_map_fd_wrapper_comment(self):
"Map a foreign data wrapper with a comment"
dbmap = self.to_map([CREATE_FDW_STMT, COMMENT_FDW_STMT])
assert dbmap['foreign data wrapper fdw1']['description'] == \
'Test foreign data wrapper fdw1'
class ForeignDataWrapperToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input foreign data wrappers"""
def test_create_fd_wrapper(self):
"Create a foreign data wrapper that didn't exist"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_FDW_STMT
def test_create_wrapper_validator(self):
"Create a foreign data wrapper with a validator function"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {
'validator': 'postgresql_fdw_validator'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE FOREIGN DATA WRAPPER fdw1 " \
"VALIDATOR postgresql_fdw_validator"
def test_create_wrapper_options(self):
"Create a foreign data wrapper with options"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {
'options': ['debug=true']}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE FOREIGN DATA WRAPPER fdw1 " \
"OPTIONS (debug 'true')"
def test_bad_map_fd_wrapper(self):
"Error creating a foreign data wrapper with a bad map"
inmap = self.std_map()
inmap.update({'fdw1': {}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_fd_wrapper(self):
"Drop an existing foreign data wrapper"
sql = self.to_sql(self.std_map(), [CREATE_FDW_STMT], superuser=True)
assert sql[0] == "DROP FOREIGN DATA WRAPPER fdw1"
def test_alter_wrapper_options(self):
"Change foreign data wrapper options"
stmts = [CREATE_FDW_STMT + " OPTIONS (opt1 'valA', opt2 'valB')"]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {
'options': ['opt1=valX', 'opt3=valY']}})
sql = self.to_sql(inmap, stmts, superuser=True)
sql = fix_indent(sql[0]).split('OPTIONS ')
assert sql[0] == "ALTER FOREIGN DATA WRAPPER fdw1 "
assert sorted(sql[1][1:-1].split(', ')) == [
"DROP opt2", "SET opt1 'valX'", "opt3 'valY'"]
def test_comment_on_fd_wrapper(self):
"Create a comment for an existing foreign data wrapper"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {
'description': "Test foreign data wrapper fdw1"}})
sql = self.to_sql(inmap, [CREATE_FDW_STMT], superuser=True)
assert sql[0] == COMMENT_FDW_STMT
class ForeignServerToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing foreign servers"""
superuser = True
def test_map_server(self):
"Map an existing foreign server"
dbmap = self.to_map([CREATE_FDW_STMT, CREATE_FS_STMT])
assert dbmap['foreign data wrapper fdw1'] == {'server fs1': {}}
def test_map_server_type_version(self):
"Map a foreign server with type and version"
stmts = [CREATE_FDW_STMT,
"CREATE SERVER fs1 TYPE 'test' VERSION '1.0' "
"FOREIGN DATA WRAPPER fdw1"]
dbmap = self.to_map(stmts)
assert dbmap['foreign data wrapper fdw1'] == {'server fs1': {
'type': 'test', 'version': '1.0'}}
def test_map_server_options(self):
"Map a foreign server with options"
stmts = [CREATE_FDW_STMT,
"CREATE SERVER fs1 FOREIGN DATA WRAPPER fdw1 "
"OPTIONS (dbname 'test')"]
dbmap = self.to_map(stmts)
assert dbmap['foreign data wrapper fdw1'] == {'server fs1': {
'options': ['dbname=test']}}
def test_map_server_comment(self):
"Map a foreign server with a comment"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, COMMENT_FS_STMT]
dbmap = self.to_map(stmts)
assert dbmap['foreign data wrapper fdw1'] == {'server fs1': {
'description': 'Test server fs1'}}
class ForeignServerToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input foreign servers"""
def test_create_server(self):
"Create a foreign server that didn't exist"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
sql = self.to_sql(inmap, [CREATE_FDW_STMT], superuser=True)
assert fix_indent(sql[0]) == CREATE_FS_STMT
def test_create_wrapper_server(self):
"Create a foreign data wrapper and its server"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_FDW_STMT
assert fix_indent(sql[1]) == CREATE_FS_STMT
def test_create_server_type_version(self):
"Create a foreign server with type and version"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'type': 'test', 'version': '1.0'}}})
sql = self.to_sql(inmap, [CREATE_FDW_STMT], superuser=True)
assert fix_indent(sql[0]) == "CREATE SERVER fs1 TYPE 'test' " \
"VERSION '1.0' FOREIGN DATA WRAPPER fdw1"
def test_create_server_options(self):
"Create a foreign server with options"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'options': ['dbname=test']}}})
sql = self.to_sql(inmap, [CREATE_FDW_STMT], superuser=True)
assert fix_indent(sql[0]) == "CREATE SERVER fs1 " \
"FOREIGN DATA WRAPPER fdw1 OPTIONS (dbname 'test')"
def test_drop_server(self):
"Drop an existing foreign server"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql[0] == "DROP SERVER fs1"
def test_add_server_options(self):
"Add options to a foreign server"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'options': ['opt1=valA', 'opt2=valB']}}})
sql = self.to_sql(inmap, stmts, superuser=True)
sql = fix_indent(sql[0]).split('OPTIONS ')
assert sql[0] == "ALTER SERVER fs1 "
assert sorted(sql[1][1:-1].split(', ')) == [
"opt1 'valA'", "opt2 'valB'"]
def test_drop_server_wrapper(self):
"Drop an existing foreign data wrapper and its server"
sql = self.to_sql(self.std_map(), [CREATE_FDW_STMT, CREATE_FS_STMT],
superuser=True)
assert sql[0] == "DROP SERVER fs1"
assert sql[1] == "DROP FOREIGN DATA WRAPPER fdw1"
def test_comment_on_server(self):
"Create a comment for an existing foreign server"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'description': "Test server fs1"}}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == [COMMENT_FS_STMT]
class UserMappingToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing user mappings"""
superuser = True
def test_map_user_mapping(self):
"Map an existing user mapping"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_UM_STMT]
dbmap = self.to_map(stmts)
assert dbmap['foreign data wrapper fdw1']['server fs1'][
'user mappings'] == {'PUBLIC': {}}
def test_map_user_mapping_options(self):
"Map a user mapping with options"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_UM_STMT +
" OPTIONS (user 'john', password 'doe')"]
dbmap = self.to_map(stmts)
assert dbmap['foreign data wrapper fdw1']['server fs1'] == {
'user mappings': {'PUBLIC': {'options': [
'password=doe', 'user=john']}}}
class UserMappingToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input user mappings"""
def test_create_user_mapping(self):
"Create a user mapping that didn't exist"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'user mappings': {'PUBLIC': {}}}}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert fix_indent(sql[0]) == CREATE_UM_STMT
def test_create_wrapper_server_mapping(self):
"Create a FDW, server and user mapping"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'user mappings': {'PUBLIC': {}}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_FDW_STMT
assert fix_indent(sql[1]) == CREATE_FS_STMT
assert fix_indent(sql[2]) == CREATE_UM_STMT
def test_create_user_mapping_options(self):
"Create a user mapping with options"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'user mappings': {'PUBLIC': {'options': [
'user=john', 'password=doe']}}}}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert fix_indent(sql[0]) == CREATE_UM_STMT + \
" OPTIONS (user 'john', password 'doe')"
def test_drop_user_mapping(self):
"Drop an existing user mapping"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_UM_STMT]
sql = self.to_sql(self.std_map(), stmts, superuser=True)
assert sql[0] == "DROP USER MAPPING FOR PUBLIC SERVER fs1"
def test_drop_user_mapping_options(self):
"Drop options from a user mapping"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT,
CREATE_UM_STMT + " OPTIONS (opt1 'valA', opt2 'valB')"]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'user mappings': {'PUBLIC': {}}}}})
sql = self.to_sql(inmap, stmts, superuser=True)
sql = fix_indent(sql[0]).split('OPTIONS ')
assert sql[0] == "ALTER USER MAPPING FOR PUBLIC SERVER fs1 "
assert sorted(sql[1][1:-1].split(', ')) == ["DROP opt1", "DROP opt2"]
class ForeignTableToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing foreign tables"""
superuser = True
def test_map_foreign_table(self):
"Map an existing foreign table"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_FT_STMT]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}], 'server': 'fs1'}
assert dbmap['schema sd']['foreign table ft1'] == expmap
def test_map_foreign_table_options(self):
"Map a foreign table with options"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_FT_STMT +
" OPTIONS (user 'jack')"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}], 'server': 'fs1',
'options': ['user=jack']}
assert dbmap['schema sd']['foreign table ft1'] == expmap
class ForeignTableToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input foreign tables"""
superuser = True
def test_create_foreign_table(self):
"Create a foreign table that didn't exist"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'foreign table ft1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'server': 'fs1'}})
sql = self.to_sql(inmap, [CREATE_FDW_STMT, CREATE_FS_STMT])
assert fix_indent(sql[0]) == CREATE_FT_STMT
def test_create_foreign_table_options(self):
"Create a foreign table with options"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'foreign table ft1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'server': 'fs1', 'options': ['user=jack']}})
sql = self.to_sql(inmap, [CREATE_FDW_STMT, CREATE_FS_STMT])
assert fix_indent(sql[0]), CREATE_FT_STMT + " OPTIONS (user 'jack')"
def test_bad_map_foreign_table(self):
"Error creating a foreign table with a bad map"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'ft1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'server': 'fs1'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_foreign_table(self):
"Drop an existing foreign table"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_FT_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
sql = self.to_sql(inmap, stmts)
assert sql[0] == "DROP FOREIGN TABLE sd.ft1"
def test_drop_foreign_table_server(self):
"Drop a foreign table and associated server"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_FT_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {}})
sql = self.to_sql(inmap, stmts)
assert sql[0] == "DROP FOREIGN TABLE sd.ft1"
assert sql[1] == "DROP SERVER fs1"
def test_alter_foreign_table_options(self):
"Alter options for a foreign table"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_FT_STMT +
" OPTIONS (opt1 'valA', opt2 'valB')"]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'foreign table ft1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'server': 'fs1', 'options': ['opt1=valX', 'opt2=valY']}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]), "ALTER FOREIGN TABLE ft1 " \
"OPTIONS (SET opt1 'valX', SET opt2 'valY')"
def test_add_column(self):
"Add new column to a foreign table"
stmts = [CREATE_FDW_STMT, CREATE_FS_STMT, CREATE_FT_STMT]
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'foreign table ft1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}], 'server': 'fs1'}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == \
"ALTER FOREIGN TABLE sd.ft1 ADD COLUMN c3 date"
+522
View File
@@ -0,0 +1,522 @@
# -*- coding: utf-8 -*-
"""Test functions"""
import pytest
from inspect import cleandoc
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
SOURCE1 = "SELECT 'dummy'::text"
CREATE_STMT1 = "CREATE FUNCTION sd.f1() RETURNS text LANGUAGE sql IMMUTABLE " \
"AS $_$%s$_$" % SOURCE1
SOURCE2 = "SELECT GREATEST($1, $2)"
CREATE_STMT2 = "CREATE FUNCTION sd.f1(integer, integer) RETURNS integer " \
"LANGUAGE sql IMMUTABLE AS $_$%s$_$" % SOURCE2
COMMENT_STMT = "COMMENT ON FUNCTION sd.f1(integer, integer) IS " \
"'Test function f1'"
SOURCE3 = "SELECT * FROM generate_series($1, $2)"
CREATE_STMT3 = "CREATE FUNCTION f2(integer, integer) RETURNS SETOF integer " \
"ROWS 20 LANGUAGE sql IMMUTABLE AS $_$%s$_$" % SOURCE3
SOURCE4 = "SELECT $1 + $2"
CREATE_STMT4 = "CREATE FUNCTION fadd(integer, integer) RETURNS integer " \
"LANGUAGE sql IMMUTABLE AS $_$%s$_$" % SOURCE4
SOURCE5 = "SELECT $1 - $2"
CREATE_STMT5 = "CREATE FUNCTION fsub(integer, integer) RETURNS integer " \
"LANGUAGE sql IMMUTABLE AS $_$%s$_$" % SOURCE5
class FunctionToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing functions"""
def test_map_function1(self):
"Map a very simple function with no arguments"
dbmap = self.to_map([CREATE_STMT1])
expmap = {'language': 'sql', 'returns': 'text',
'source': SOURCE1, 'volatility': 'immutable'}
assert dbmap['schema sd']['function f1()'] == expmap
def test_map_function_with_args(self):
"Map a function with two arguments"
stmts = ["CREATE FUNCTION f1(integer, integer) RETURNS integer "
"LANGUAGE sql AS $_$%s$_$" % SOURCE2]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['function f1(integer, integer)'] == \
{'language': 'sql', 'returns': 'integer', 'source': SOURCE2}
def test_map_function_default_args(self):
"Map a function with default arguments"
stmts = ["CREATE FUNCTION f1(integer, integer) RETURNS integer "
"LANGUAGE sql AS $_$%s$_$" % SOURCE2]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['function f1(integer, integer)'] == \
{'language': 'sql', 'returns': 'integer', 'source': SOURCE2}
def test_map_void_function(self):
"Map a function returning void"
stmts = ["CREATE TABLE t1 (c1 integer, c2 text)",
"CREATE FUNCTION f1() RETURNS void LANGUAGE sql AS "
"$_$INSERT INTO t1 VALUES (1, 'dummy')$_$"]
dbmap = self.to_map(stmts)
expmap = {'language': 'sql', 'returns': 'void',
'source': "INSERT INTO t1 VALUES (1, 'dummy')"}
assert dbmap['schema sd']['function f1()'] == expmap
def test_map_setof_row_function(self):
"Map a function returning a set of rows"
stmts = ["CREATE TABLE t1 (c1 integer, c2 text)",
"CREATE FUNCTION f1() RETURNS SETOF t1 LANGUAGE sql AS "
"$_$SELECT * FROM t1$_$"]
dbmap = self.to_map(stmts)
expmap = {'language': 'sql', 'returns': 'SETOF sd.t1',
'source': "SELECT * FROM t1"}
assert dbmap['schema sd']['function f1()'] == expmap
def test_map_security_definer_function(self):
"Map a function that is SECURITY DEFINER"
stmts = ["CREATE FUNCTION f1() RETURNS text LANGUAGE sql "
"SECURITY DEFINER AS $_$%s$_$" % SOURCE1]
dbmap = self.to_map(stmts)
expmap = {'language': 'sql', 'returns': 'text',
'source': SOURCE1, 'security_definer': True}
assert dbmap['schema sd']['function f1()'] == expmap
def test_map_c_lang_function(self):
"Map a dynamically loaded C language function"
# NOTE 1: Needs contrib/spi module to be available
# NOTE 2: Needs superuser privilege
stmts = ["CREATE FUNCTION autoinc() RETURNS trigger "
"AS '$libdir/autoinc' LANGUAGE c"]
dbmap = self.to_map(stmts, superuser=True)
expmap = {'language': 'c', 'obj_file': '$libdir/autoinc',
'link_symbol': 'autoinc', 'returns': 'trigger'}
assert dbmap['schema sd']['function autoinc()'] == expmap
def test_map_function_config(self):
"Map a function with a configuration parameter"
stmts = ["CREATE FUNCTION f1() RETURNS date LANGUAGE sql SET "
"datestyle to postgres, dmy AS $_$SELECT CURRENT_DATE$_$"]
dbmap = self.to_map(stmts)
expmap = {'language': 'sql', 'returns': 'date',
'configuration': ['DateStyle=postgres, dmy'],
'source': "SELECT CURRENT_DATE"}
assert dbmap['schema sd']['function f1()'] == expmap
def test_map_function_comment(self):
"Map a function comment"
dbmap = self.to_map([CREATE_STMT2, COMMENT_STMT])
assert dbmap['schema sd']['function f1(integer, integer)'][
'description'] == 'Test function f1'
def test_map_function_rows(self):
"Map a function rows"
dbmap = self.to_map([CREATE_STMT3])
assert dbmap['schema sd']['function f2(integer, integer)'][
'rows'] == 20
def test_map_function_leakproof(self):
"Map a function with LEAKPROOF qualifier"
stmt = CREATE_STMT4.replace("IMMUTABLE", "IMMUTABLE LEAKPROOF")
dbmap = self.to_map([stmt], superuser=True)
expmap = {'language': 'sql', 'returns': 'integer', 'leakproof': True,
'source': SOURCE4, 'volatility': 'immutable'}
assert dbmap['schema sd']['function fadd(integer, integer)'] == \
expmap
class FunctionToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input functions"""
def test_create_function1(self):
"Create a very simple function with no arguments"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1,
'volatility': 'immutable'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == CREATE_STMT1
def test_create_function_with_args(self):
"Create a function with two arguments"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE2}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.f1(integer, integer)"\
" RETURNS integer LANGUAGE sql AS $_$%s$_$" % SOURCE2
def test_create_setof_row_function(self):
"Create a function returning a set of rows"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
inmap['schema sd'].update({
'function f1()': {'language': 'sql', 'returns': 'SETOF t1',
'source': "SELECT * FROM t1"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[2]) == "CREATE FUNCTION sd.f1() RETURNS " \
"SETOF t1 LANGUAGE sql AS $_$SELECT * FROM t1$_$"
def test_create_setof_row_function_rows(self):
"Create a function returning a set of rows with suggested number"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
inmap['schema sd'].update({
'function f1()': {'language': 'sql', 'returns': 'SETOF t1',
'source': "SELECT * FROM t1", 'rows': 50}})
sql = self.to_sql(inmap)
assert fix_indent(sql[2]) == "CREATE FUNCTION sd.f1() RETURNS SETOF " \
"t1 LANGUAGE sql ROWS 50 AS $_$SELECT * FROM t1$_$"
def test_create_security_definer_function(self):
"Create a SECURITY DEFINER function"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1,
'security_definer': True}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.f1() RETURNS text " \
"LANGUAGE sql SECURITY DEFINER AS $_$%s$_$" % SOURCE1
def test_create_c_lang_function(self):
"Create a dynamically loaded C language function"
# NOTE 1: Needs contrib/spi module to be available
# NOTE 2: Needs superuser privilege
inmap = self.std_map()
inmap['schema sd'].update({'function autoinc()': {
'language': 'c', 'returns': 'trigger',
'obj_file': '$libdir/autoinc'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE FUNCTION sd.autoinc() " \
"RETURNS trigger LANGUAGE c AS '$libdir/autoinc', 'autoinc'"
def test_create_function_config(self):
"Create a function with a configuration parameter"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'sql', 'returns': 'date',
'configuration': ['DateStyle=postgres, dmy'],
'source': "SELECT CURRENT_DATE"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.f1() RETURNS date " \
"LANGUAGE sql SET DateStyle=postgres, dmy AS " \
"$_$SELECT CURRENT_DATE$_$"
def test_create_function_in_schema(self):
"Create a function within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'function f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1,
'volatility': 'immutable'}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[1]) == "CREATE FUNCTION s1.f1() RETURNS text " \
"LANGUAGE sql IMMUTABLE AS $_$%s$_$" % SOURCE1
def test_bad_function_map(self):
"Error creating a function with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_function1(self):
"Drop an existing function with no arguments"
sql = self.to_sql(self.std_map(), [CREATE_STMT1])
assert sql == ["DROP FUNCTION sd.f1()"]
def test_drop_function_with_args(self):
"Drop an existing function which has arguments"
sql = self.to_sql(self.std_map(), [CREATE_STMT2])
assert sql == ["DROP FUNCTION sd.f1(integer, integer)"]
def test_change_function_defn(self):
"Change function definition"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'sql', 'returns': 'text',
'source': "SELECT 'example'::text", 'volatility': 'immutable'}})
sql = self.to_sql(inmap, [CREATE_STMT1])
assert fix_indent(sql[1]) == "CREATE OR REPLACE FUNCTION sd.f1() " \
"RETURNS text LANGUAGE sql IMMUTABLE AS " \
"$_$SELECT 'example'::text$_$"
def test_function_with_comment(self):
"Create a function with a comment"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'description': 'Test function f1', 'language': 'sql',
'returns': 'integer', 'source': SOURCE2}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.f1(integer, integer)"\
" RETURNS integer LANGUAGE sql AS $_$%s$_$" % SOURCE2
assert sql[2] == COMMENT_STMT
def test_comment_on_function(self):
"Create a comment for an existing function"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'description': 'Test function f1', 'language': 'sql',
'returns': 'integer', 'source': SOURCE2}})
sql = self.to_sql(inmap, [CREATE_STMT2])
assert sql == [COMMENT_STMT]
def test_drop_function_comment(self):
"Drop a comment on an existing function"
stmts = [CREATE_STMT2, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE2}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON FUNCTION sd.f1(integer, integer) IS NULL"]
def test_change_function_comment(self):
"Change existing comment on a function"
stmts = [CREATE_STMT2, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'description': 'Changed function f1', 'language': 'sql',
'returns': 'integer', 'source': SOURCE2}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON FUNCTION sd.f1(integer, integer) IS "
"'Changed function f1'"]
def test_function_leakproof(self):
"Create a function with LEAKPROOF qualifier"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'leakproof': True,
'source': SOURCE4, 'volatility': 'immutable'}})
sql = self.to_sql(inmap, superuser=True)
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.f1(integer, integer)"\
" RETURNS integer LANGUAGE sql IMMUTABLE LEAKPROOF AS " \
"$_$%s$_$" % SOURCE4
def test_alter_function_leakproof(self):
"Change a function with LEAKPROOF qualifier"
inmap = self.std_map()
inmap['schema sd'].update({
'function fadd(integer, integer)': {
'language': 'sql', 'returns': 'integer',
'source': SOURCE4, 'volatility': 'immutable'}})
stmt = CREATE_STMT4.replace("IMMUTABLE", "IMMUTABLE LEAKPROOF")
sql = self.to_sql(inmap, [stmt], superuser=True)
assert fix_indent(sql[0]) == \
"ALTER FUNCTION sd.fadd(integer, integer) NOT LEAKPROOF"
def test_change_function_return_type(self):
source = lambda rtype: "SELECT '127.0.0.1'::{}".format(rtype)
old_type = 'text'
new_type = 'inet'
statement = lambda rtype: cleandoc("""
CREATE OR REPLACE FUNCTION sd.fget_addr()
RETURNS {rtype}
LANGUAGE sql
IMMUTABLE
AS $_${body}$_$"""
).format(
rtype=rtype,
body=source(rtype),
).replace('\n', ' ')
inmap = self.std_map()
inmap['schema sd'].update({
'function fget_addr()': {
'language': 'sql',
'returns': new_type,
'source': source(new_type),
}
})
sql = self.to_sql(inmap, [statement(old_type)])
assert statement(new_type) == fix_indent(sql[1])
class AggregateToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing aggregates"""
def test_map_aggregate_simple(self):
"Map a simple aggregate"
stmts = [CREATE_STMT2, "CREATE AGGREGATE a1 (integer) ("
"SFUNC = f1, STYPE = integer)"]
dbmap = self.to_map(stmts)
expmap = {'sfunc': 'f1', 'stype': 'integer'}
assert dbmap['schema sd']['function f1(integer, integer)'] == \
{'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'volatility': 'immutable'}
assert dbmap['schema sd']['aggregate a1(integer)'] == expmap
def test_map_aggregate_init_final(self):
"Map an aggregate with an INITCOND and a FINALFUNC"
stmts = [CREATE_STMT2,
"CREATE FUNCTION f2(integer) RETURNS float "
"LANGUAGE sql AS $_$SELECT $1::float$_$ IMMUTABLE",
"CREATE AGGREGATE a1 (integer) (SFUNC = f1, STYPE = integer, "
"FINALFUNC = f2, INITCOND = '-1')"]
dbmap = self.to_map(stmts)
expmap = {'sfunc': 'f1', 'stype': 'integer',
'initcond': '-1', 'finalfunc': 'f2'}
assert dbmap['schema sd']['function f1(integer, integer)'] == \
{'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'volatility': 'immutable'}
assert dbmap['schema sd']['function f2(integer)'] == \
{'language': 'sql', 'returns': 'double precision',
'source': "SELECT $1::float", 'volatility': 'immutable'}
assert dbmap['schema sd']['aggregate a1(integer)'] == expmap
def test_map_aggregate_sortop(self):
"Map an aggregate with a SORTOP"
stmts = [CREATE_STMT2, "CREATE AGGREGATE a1 (integer) ("
"SFUNC = f1, STYPE = integer, SORTOP = >)"]
dbmap = self.to_map(stmts)
expmap = {'sfunc': 'f1', 'stype': 'integer',
'sortop': 'pg_catalog.>'}
assert dbmap['schema sd']['aggregate a1(integer)'] == expmap
def test_map_moving_aggregate(self):
"Map a moving-aggregate mode function"
stmts = [CREATE_STMT4, CREATE_STMT5,
"CREATE AGGREGATE a1 (integer) (sfunc = fadd, "
"stype = integer, initcond = '0', msfunc = fadd, "
"minvfunc = fsub, mstype = integer, minitcond = '0')"]
dbmap = self.to_map(stmts)
expmap = {'sfunc': 'fadd', 'stype': 'integer', 'initcond': '0',
'msfunc': 'fadd', 'minvfunc': 'fsub', 'mstype': 'integer',
'minitcond': '0'}
assert dbmap['schema sd']['aggregate a1(integer)'] == expmap
def test_map_ordered_set_aggregate(self):
"Map an ordered-set aggregate"
stmts = [CREATE_STMT2, "CREATE AGGREGATE a1 (integer ORDER BY "
"integer) (sfunc = f1, stype = integer)"]
dbmap = self.to_map(stmts)
expmap = {'sfunc': 'f1', 'stype': 'integer', 'kind': 'ordered'}
assert dbmap['schema sd'][
'aggregate a1(integer ORDER BY integer)'] == expmap
def test_map_aggregate_restricted(self):
"Map an aggregate with restricted parallel safety"
stmts = [CREATE_STMT2, "CREATE AGGREGATE a1 (integer) ("
"SFUNC = f1, STYPE = integer, PARALLEL = RESTRICTED)"]
dbmap = self.to_map(stmts)
expmap = {'sfunc': 'f1', 'stype': 'integer', 'parallel': 'restricted'}
assert dbmap['schema sd']['aggregate a1(integer)'] == expmap
class AggregateToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input aggregates"""
def test_create_aggregate_simple(self):
"Create a simple aggregate"
inmap = self.std_map()
inmap['schema sd'].update({'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'volatility': 'immutable'}})
inmap['schema sd'].update({'aggregate a1(integer)': {
'sfunc': 'f1', 'stype': 'integer'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == CREATE_STMT2
assert fix_indent(sql[2]) == "CREATE AGGREGATE sd.a1(integer) " \
"(SFUNC = sd.f1, STYPE = integer)"
def test_create_aggregate_sortop(self):
"Create an aggregate that specifies a sort operator"
inmap = self.std_map()
inmap['schema sd'].update({'function f1(float, float)': {
'language': 'sql', 'returns': 'float', 'source': SOURCE2,
'volatility': 'immutable'}})
inmap['schema sd'].update({'aggregate a1(float)': {
'sfunc': 'f1', 'stype': 'float', 'sortop': 'pg_catalog.>'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[2]) == "CREATE AGGREGATE sd.a1(float) " \
"(SFUNC = sd.f1, STYPE = float, SORTOP = OPERATOR(pg_catalog.>))"
def test_create_aggregate_init_final(self):
"Create an aggregate with an INITCOND and a FINALFUNC"
inmap = self.std_map()
inmap['schema sd'].update({'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'volatility': 'immutable'}})
inmap['schema sd'].update({'function f2(integer)': {
'language': 'sql', 'returns': 'double precision',
'source': "SELECT $1::float", 'volatility': 'immutable'}})
inmap['schema sd'].update({'aggregate a1(integer)': {
'sfunc': 'f1', 'stype': 'integer', 'initcond': '-1',
'finalfunc': 'f2'}})
sql = self.to_sql(inmap)
funcs = sorted(sql[1:3])
assert fix_indent(funcs[0]) == CREATE_STMT2
assert fix_indent(funcs[1]) == "CREATE FUNCTION sd.f2(integer) " \
"RETURNS double precision LANGUAGE sql IMMUTABLE " \
"AS $_$SELECT $1::float$_$"
assert fix_indent(sql[3]) == "CREATE AGGREGATE sd.a1(integer) " \
"(SFUNC = sd.f1, STYPE = integer, FINALFUNC = sd.f2, " \
"INITCOND = '-1')"
def test_drop_aggregate(self):
"Drop an existing aggregate"
stmts = [CREATE_STMT2, "CREATE AGGREGATE agg1 (integer) "
"(SFUNC = f1, STYPE = integer)"]
sql = self.to_sql(self.std_map(), stmts)
assert sql[0] == "DROP AGGREGATE sd.agg1(integer)"
assert sql[1] == "DROP FUNCTION sd.f1(integer, integer)"
def test_create_moving_aggregate(self):
"Create a moving-aggregate mode function"
inmap = self.std_map()
inmap['schema sd'].update(
{'function fadd(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE4,
'volatility': 'immutable'},
'function fsub(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE5,
'volatility': 'immutable'},
'aggregate a1(integer)': {
'sfunc': 'fadd', 'stype': 'integer', 'initcond': '0',
'msfunc': 'fadd', 'minvfunc': 'fsub', 'mstype': 'integer',
'minitcond': '0'}})
sql = self.to_sql(inmap, [CREATE_STMT4, CREATE_STMT5])
assert fix_indent(sql[0]) == "CREATE AGGREGATE sd.a1(integer) (" \
"SFUNC = sd.fadd, STYPE = integer, INITCOND = '0', " \
"MSFUNC = sd.fadd, MINVFUNC = sd.fsub, MSTYPE = integer, " \
"MINITCOND = '0')"
def test_create_hypothetical_set_aggregate(self):
"Create a hypothetical-set aggregate"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'volatility': 'immutable'},
'aggregate a1(integer ORDER BY integer)': {
'kind': 'hypothetical', 'sfunc': 'f1', 'stype': 'integer'}})
sql = self.to_sql(inmap, [CREATE_STMT2])
assert fix_indent(sql[0]) == "CREATE AGGREGATE sd.a1(integer " \
"ORDER BY integer) (SFUNC = sd.f1, STYPE = integer, HYPOTHETICAL)"
def test_create_aggregate_parallel_safe(self):
"Create an aggregate with parallel safety"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, integer)': {
'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'volatility': 'immutable'},
'aggregate a1(integer ORDER BY integer)': {
'sfunc': 'f1', 'stype': 'integer', 'parallel': 'safe'}})
sql = self.to_sql(inmap, [CREATE_STMT2])
assert fix_indent(sql[0]) == "CREATE AGGREGATE sd.a1(integer " \
"ORDER BY integer) (SFUNC = sd.f1, STYPE = integer, " \
"PARALLEL = SAFE)"
+376
View File
@@ -0,0 +1,376 @@
# -*- coding: utf-8 -*-
"""Test indexes"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TABLE_STMT = "CREATE TABLE t1 (c1 integer, c2 text)"
CREATE_STMT = "CREATE INDEX t1_idx ON t1 (c1)"
COMMENT_STMT = "COMMENT ON INDEX sd.t1_idx IS 'Test index t1_idx'"
class IndexToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created indexes"""
def test_index_1(self):
"Map a single-column index"
dbmap = self.to_map([CREATE_TABLE_STMT, CREATE_STMT])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1']}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_index_2(self):
"Map a two-column index"
stmts = ["CREATE TABLE t1 (c1 INTEGER, c2 CHAR(5), c3 TEXT)",
"CREATE UNIQUE INDEX t1_idx ON t1 (c1, c2)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'character(5)'}},
{'c3': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1', 'c2'],
'unique': True}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_index_3(self):
"Map a table with a unique index and a non-unique GIN index"
stmts = ["CREATE TABLE t1 (c1 INTEGER, c2 CHAR(5), c3 tsvector)",
"CREATE UNIQUE INDEX t1_idx_1 ON t1 (c1, c2)",
"CREATE INDEX t1_idx_2 ON t1 USING gin (c3)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'character(5)'}},
{'c3': {'type': 'tsvector'}}],
'indexes': {'t1_idx_1': {'keys': ['c1', 'c2'],
'unique': True},
't1_idx_2': {'keys': ['c3'],
'access_method': 'gin'}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_index_partial(self):
"Map a table with a partial index"
dbmap = self.to_map([CREATE_TABLE_STMT,
"CREATE INDEX t1_idx ON t1 (c2) WHERE c1 > 42"])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c2'],
'predicate': '(c1 > 42)'}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_index_function_simple(self):
"Map an index using a function -- issue #98"
stmts = [CREATE_TABLE_STMT, "CREATE INDEX t1_idx ON t1 ((lower(c2)))"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': [
{'lower(c2)': {'type': 'expression'}}]}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_index_function_complex(self):
"Map indexes using nested functions and complex arguments"
stmts = ["CREATE TABLE t1 (c1 integer, c2 text, c3 date)",
"CREATE INDEX t1_idx1 ON t1 (substring(c2 from position("
"'_begin' in c2)), substring(c2 from position('_end' in "
"c2)))",
"CREATE INDEX t1_idx2 ON t1 (extract(month from c3), "
"extract(day from c3))"]
dbmap = self.to_map(stmts)
expr_type = {'type': 'expression'}
key1 = [{"SUBSTRING(c2 FROM POSITION(('_begin'::text) IN (c2)))":
expr_type},
{"SUBSTRING(c2 FROM POSITION(('_end'::text) IN (c2)))":
expr_type}]
key2 = [{"EXTRACT(month FROM c3)": expr_type},
{"EXTRACT(day FROM c3)": expr_type}]
if self.db.version < 150000:
key1 = [{'"substring"(c2, "position"(c2, \'_begin\''
'::text))': expr_type},
{'"substring"(c2, "position"(c2, \'_end\''
'::text))': expr_type}]
key2 = [{"date_part('month'::text, c3)": {
'type': 'expression'}},
{"date_part('day'::text, c3)": {
'type': 'expression'}}]
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'indexes': {'t1_idx1': {'keys': key1},
't1_idx2': {'keys': key2}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_index_col_opts(self):
"Map an index with various column options"
stmts = ["CREATE TABLE t1 (c1 cidr, c2 text)",
"CREATE INDEX t1_idx ON t1 (c1 cidr_ops NULLS FIRST, "
"c2 DESC)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'cidr'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': [
{'c1': {'opclass': 'cidr_ops', 'nulls': 'first'}},
{'c2': {'order': 'desc'}}]}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_index_mixed(self):
"Map indexes using functions, a regular column and expressions"
stmts = ["CREATE TABLE t1 (c1 integer, c2 text, c3 text)",
"CREATE INDEX t1_idx ON t1 (btrim(c3, 'x') NULLS FIRST, c1, "
"lower(c2) DESC)",
"CREATE INDEX t1_idx2 ON t1 ((c2 || ', ' || c3), "
"(c3 || ' ' || c2))"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}},
{'c3': {'type': 'text'}}],
'indexes': {'t1_idx': {
'keys': [{"btrim(c3, 'x'::text)": {
'type': 'expression', 'nulls': 'first'}},
'c1', {'lower(c2)': {
'type': 'expression', 'order': 'desc'}}]},
't1_idx2': {
'keys': [{"(((c2 || ', '::text) || c3))": {
'type': 'expression'}},
{"(((c3 || ' '::text) || c2))": {
'type': 'expression'}}]}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_index_cluster(self):
"Map a table with an index and cluster on it"
dbmap = self.to_map([CREATE_TABLE_STMT, CREATE_STMT,
"CLUSTER t1 USING t1_idx"])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'], 'cluster': True}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_index_comment(self):
"Map an index comment"
dbmap = self.to_map([CREATE_TABLE_STMT, CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['table t1']['indexes']['t1_idx'][
'description'] == 'Test index t1_idx'
def test_map_multicol_index_with_exprs(self):
"Map a multicol index with expressions"
dbmap = self.to_map(["CREATE TABLE holiday (id serial PRIMARY KEY,"
"date date NOT NULL, recurring boolean NOT NULL)",
"CREATE UNIQUE INDEX unique_date ON holiday (("
"CASE WHEN recurring THEN (0)::double precision "
"ELSE date_part('year'::text, date) END), "
"date_part('month'::text, date), "
"date_part('day'::text, date))"])
fmt = "(\nCASE\n %s\n %s\nEND)"
assert dbmap['schema sd']['table holiday']['indexes'][
'unique_date'] == {
'keys': [
{fmt % ("WHEN recurring THEN (0)::double precision",
"ELSE date_part('year'::text, date)"):
{'type': 'expression'}},
{"date_part('month'::text, date)": {'type': 'expression'}},
{"date_part('day'::text, date)": {'type': 'expression'}}],
'unique': True}
class IndexToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input indexes"""
def test_create_table_with_index(self):
"Create new table with a single column index"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1']}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TABLE sd.t1 (c1 integer, c2 text)"
assert sql[1] == "CREATE INDEX t1_idx ON sd.t1 (c1)"
def test_add_index(self):
"Add a two-column unique index to an existing table"
stmts = ["CREATE TABLE t1 (c1 INTEGER NOT NULL, "
"c2 INTEGER NOT NULL, c3 TEXT)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'integer', 'not_null': True}},
{'c3': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c2', 'c1'], 'unique': True}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["CREATE UNIQUE INDEX t1_idx ON sd.t1 (c2, c1)"]
def test_add_index_schema(self):
"Add an index to an existing table in a non-default schema"
stmts = ["CREATE SCHEMA s1",
"CREATE TABLE s1.t1 (c1 INTEGER NOT NULL, "
"c2 INTEGER NOT NULL, c3 TEXT)"]
inmap = self.std_map()
inmap.update({'schema s1': {'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'integer', 'not_null': True}},
{'c3': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c2', 'c1'], 'unique': True}}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["CREATE UNIQUE INDEX t1_idx ON s1.t1 (c2, c1)"]
def test_add_index_back_compat(self):
"Add a index to an existing table accepting back-compatible spec"
stmts = ["CREATE TABLE t1 (c1 INTEGER NOT NULL, "
"c2 INTEGER NOT NULL, c3 TEXT)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'integer', 'not_null': True}},
{'c3': {'type': 'text'}}],
'indexes': {'t1_idx': {'columns': ['c2', 'c1'], 'unique': True,
'access_method': 'btree'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["CREATE UNIQUE INDEX t1_idx ON sd.t1 (c2, c1)"]
def test_bad_index(self):
"Fail on creating an index without columns or expression"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'access_method': 'btree'}}}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_create_partial(self):
"Create a partial index"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c2'],
'predicate': '(c1 > 42)'}}}})
sql = self.to_sql(inmap, [CREATE_TABLE_STMT])
assert fix_indent(sql[0]) == \
"CREATE INDEX t1_idx ON sd.t1 (c2) WHERE (c1 > 42)"
def test_create_index_function(self):
"Create an index which uses a function"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': [{
'lower(c2)': {'type': 'expression'}}]}}}})
sql = self.to_sql(inmap)
assert sql[1] == "CREATE INDEX t1_idx ON sd.t1 (lower(c2))"
def test_create_index_col_opts(self):
"Create table and an index with column options"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'cidr'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': [{'c1': {
'opclass': 'cidr_ops', 'nulls': 'first'}}, 'c2']}}}})
sql = self.to_sql(inmap)
assert sql[1] == "CREATE INDEX t1_idx ON sd.t1 " \
"(c1 cidr_ops NULLS FIRST, c2)"
def test_create_index_mixed(self):
"Create indexes using functions, a regular column and expressions"
stmts = ["CREATE TABLE t1 (c1 integer, c2 text, c3 text)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': [
{"btrim(c3, 'x'::text)": {'type': 'expression',
'nulls': 'first'}}, 'c1',
{'lower(c2)': {'type': 'expression', 'order': 'desc'}}]},
't1_idx2': {'keys': [
{"(((c2 || ', '::text) || c3))": {'type': 'expression'}},
{"(((c3 || ' '::text) || c2))": {
'type': 'expression'}}]}}}})
sql = sorted(self.to_sql(inmap, stmts))
assert sql[0] == "CREATE INDEX t1_idx ON sd.t1 (" \
"btrim(c3, 'x'::text) NULLS FIRST, c1, lower(c2) DESC)"
assert sql[1] == "CREATE INDEX t1_idx2 ON sd.t1 (" \
"(((c2 || ', '::text) || c3)), (((c3 || ' '::text) || c2)))"
def test_create_table_with_index_clustered(self):
"Create new table clustered on a single column index"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'], 'cluster': True}}}})
sql = self.to_sql(inmap)
assert sql[2] == "CLUSTER sd.t1 USING t1_idx"
def test_cluster_table_with_index(self):
"Change a table with an index to cluster on it"
stmts = [CREATE_TABLE_STMT, CREATE_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'], 'cluster': True}}}})
sql = self.to_sql(inmap, stmts)
assert sql[0] == "CLUSTER sd.t1 USING t1_idx"
def test_uncluster_table_with_index(self):
"Change a table clustered on an index to remove cluster"
stmts = [CREATE_TABLE_STMT, CREATE_STMT, "CLUSTER t1 USING t1_idx"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1']}}}})
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 SET WITHOUT CLUSTER"
def test_comment_on_index(self):
"Create a comment for an existing index"
stmts = [CREATE_TABLE_STMT, CREATE_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'],
'description': 'Test index t1_idx'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == [COMMENT_STMT]
def test_drop_index_comment(self):
"Drop the comment on an existing index"
stmts = [CREATE_TABLE_STMT, CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1']}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON INDEX sd.t1_idx IS NULL"]
def test_change_index_comment(self):
"Change existing comment on an index"
stmts = [CREATE_TABLE_STMT, CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'],
'description': 'Changed index t1_idx'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON INDEX sd.t1_idx IS 'Changed index t1_idx'"]
def test_change_index_keys(self):
"Change keys of an existing index"
stmts = [CREATE_TABLE_STMT, CREATE_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['btrim(c1)']}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["DROP INDEX sd.t1_idx",
"CREATE INDEX t1_idx ON sd.t1 (btrim(c1))"]
def test_change_order_index_keys(self):
"Change keys order of an existing index"
stmts = [CREATE_TABLE_STMT, "CREATE INDEX t1_idx ON t1 (c1, c2)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c2', 'c1']}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["DROP INDEX sd.t1_idx",
"CREATE INDEX t1_idx ON sd.t1 (c2, c1)"]
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""Test materialized views"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TABLE = "CREATE TABLE t1 (c1 INTEGER, c2 TEXT, c3 INTEGER)"
VIEW_STMT = "SELECT c1, c3 * 2 AS mc3 FROM t1"
CREATE_STMT = "CREATE MATERIALIZED VIEW sd.mv1 AS " + VIEW_STMT
COMMENT_STMT = "COMMENT ON MATERIALIZED VIEW sd.mv1 IS 'Test matview mv1'"
VIEW_DEFN = " SELECT t1.c1,\n t1.c3 * 2 AS mc3\n FROM sd.t1;"
class MatViewToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created materialized views"""
def test_map_view_simple(self):
"Map a created materialized view"
stmts = [CREATE_TABLE, CREATE_STMT]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'mc3': {'type': 'integer'}}],
'definition': VIEW_DEFN, 'with_data': True,
'depends_on': ['table t1']}
assert dbmap['schema sd']['materialized view mv1'] == expmap
def test_map_view_comment(self):
"Map a materialized view with a comment"
dbmap = self.to_map([CREATE_TABLE, CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['materialized view mv1'][
'description'] == 'Test matview mv1'
def test_map_view_index(self):
"Map a materialized view with an index"
stmts = [CREATE_TABLE, CREATE_STMT,
"CREATE INDEX idx1 ON mv1 (mc3)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'mc3': {'type': 'integer'}}],
'definition': VIEW_DEFN, 'with_data': True,
'indexes': {'idx1': {'keys': ['mc3']}},
'depends_on': ['table t1']}
assert dbmap['schema sd']['materialized view mv1'] == expmap
class MatViewToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input materialized views"""
def test_create_view(self):
"Create a materialized view"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'integer'}}]}})
inmap['schema sd'].update({'materialized view mv1': {
'columns': [{'c1': {'type': 'integer'}},
{'mc3': {'type': 'integer'}}],
'definition': "SELECT c1, c3 * 2 AS mc3 FROM sd.t1",
'depends_on': ['table t1']}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TABLE sd.t1 (c1 integer, " \
"c2 text, c3 integer)"
assert fix_indent(sql[1]) == "CREATE MATERIALIZED VIEW sd.mv1 AS " \
"SELECT c1, c3 * 2 AS mc3 FROM sd.t1"
def test_bad_view_map(self):
"Error creating a materialized view with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'mv1': {'definition': VIEW_DEFN}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_view(self):
"Drop an existing materialized view with table dependencies"
stmts = ["CREATE TABLE t1 (c1 INTEGER, c2 TEXT)",
"CREATE TABLE t2 (c1 INTEGER, c3 TEXT)",
"CREATE MATERIALIZED VIEW mv1 AS SELECT t1.c1, c2, c3 "
"FROM t1 JOIN t2 ON (t1.c1 = t2.c1)"]
sql = self.to_sql(self.std_map(), stmts)
assert sql[0] == "DROP MATERIALIZED VIEW sd.mv1"
# can't control which table will be dropped first
drt1 = 1
drt2 = 2
if 't1' in sql[2]:
drt1 = 2
drt2 = 1
assert sql[drt1] == "DROP TABLE sd.t1"
assert sql[drt2] == "DROP TABLE sd.t2"
def test_view_with_comment(self):
"Create a materialized view with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'materialized view mv1': {
'columns': [{'c1': {'type': 'integer'}},
{'mc3': {'type': 'integer'}}],
'definition': VIEW_STMT, 'description': "Test matview mv1"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_view_index(self):
"Create an index on a materialized view"
stmts = [CREATE_TABLE, CREATE_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'integer'}}]}})
inmap['schema sd'].update({'materialized view mv1': {
'columns': [{'c1': {'type': 'integer'}},
{'mc3': {'type': 'integer'}}],
'definition': VIEW_DEFN, 'indexes': {'idx1': {'keys': ['mc3']}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["CREATE INDEX idx1 ON sd.mv1 (mc3)"]
+133
View File
@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
"""Test operators"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE OPERATOR sd.+ (PROCEDURE = textcat, LEFTARG = text, " \
"RIGHTARG = text)"
COMMENT_STMT = "COMMENT ON OPERATOR sd.+(text, text) IS 'Test operator +'"
class OperatorToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing operators"""
def test_map_operator1(self):
"Map a simple operator"
dbmap = self.to_map([CREATE_STMT])
expmap = {'procedure': 'textcat'}
assert dbmap['schema sd']['operator +(text, text)'] == expmap
def test_map_operator_rightarg(self):
"Map a unitary operator with a right argument"
stmts = ["CREATE OPERATOR + (PROCEDURE = upper, RIGHTARG = text)"]
dbmap = self.to_map(stmts)
expmap = {'procedure': 'pg_catalog.upper'}
assert dbmap['schema sd']['operator +(NONE, text)'] == expmap
def test_map_operator_commutator(self):
"Map an operator with a commutator"
stmts = ["CREATE OPERATOR && (PROCEDURE = int4pl, LEFTARG = integer, "
"RIGHTARG = integer, COMMUTATOR = OPERATOR(sd.&&))"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['operator &&(integer, integer)'] == \
{'procedure': 'int4pl', 'commutator': 'sd.&&'}
def test_map_operator_hash(self):
"Map an operator with HASHES clause"
stmts = ["CREATE OPERATOR + (PROCEDURE = texteq, LEFTARG = text, "
"RIGHTARG = text, HASHES)"]
dbmap = self.to_map(stmts)
expmap = {'procedure': 'texteq', 'hashes': True}
assert dbmap['schema sd']['operator +(text, text)'] == expmap
def test_map_operator_comment(self):
"Map a operator comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['operator +(text, text)'][
'description'] == 'Test operator +'
class OperatorToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input operators"""
def test_create_operator1(self):
"Create a simple operator"
inmap = self.std_map()
inmap['schema sd'].update({'operator +(text, text)': {
'procedure': 'textcat'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
def test_create_operator_rightarg(self):
"Create a unitary operator with a right argument"
inmap = self.std_map()
inmap['schema sd'].update({'operator +(NONE, text)': {
'procedure': 'upper'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE OPERATOR sd.+ (" \
"PROCEDURE = upper, RIGHTARG = text)"
def test_create_operator_commutator(self):
"Create an operator with a commutator"
inmap = self.std_map()
inmap['schema sd'].update({'operator &&(integer, integer)': {
'procedure': 'int4pl', 'commutator': 'sd.&&'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE OPERATOR sd.&& (" \
"PROCEDURE = int4pl, LEFTARG = integer, RIGHTARG = integer, " \
"COMMUTATOR = OPERATOR(sd.&&))"
def test_create_operator_in_schema(self):
"Create a operator within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'operator +(text, text)': {
'procedure': 'textcat'}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE OPERATOR s1.+ " \
"(PROCEDURE = textcat, LEFTARG = text, RIGHTARG = text)"
def test_drop_operator(self):
"Drop an existing operator"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql == ["DROP OPERATOR sd.+(text, text)"]
def test_operator_with_comment(self):
"Create a operator with a comment"
inmap = self.std_map()
inmap['schema sd'].update({
'operator +(text, text)': {'description': 'Test operator +',
'procedure': 'textcat'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_comment_on_operator(self):
"Create a comment for an existing operator"
inmap = self.std_map()
inmap['schema sd'].update({
'operator +(text, text)': {'description': 'Test operator +',
'procedure': 'textcat'}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == [COMMENT_STMT]
def test_drop_operator_comment(self):
"Drop a comment on an existing operator"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({
'operator +(text, text)': {'returns': 'integer',
'procedure': 'textcat'}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON OPERATOR sd.+(text, text) IS NULL"]
def test_change_operator_comment(self):
"Change existing comment on a operator"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({
'operator +(text, text)': {'description': 'Changed operator +',
'procedure': 'textcat'}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON OPERATOR sd.+(text, text) IS "
"'Changed operator +'"]
+160
View File
@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
"""Test operator classes"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TYPE_STMT = """
CREATE TYPE myint;
CREATE FUNCTION myintin(cstring) RETURNS myint AS 'int4in' LANGUAGE internal;
CREATE FUNCTION myintout(myint) RETURNS cstring AS 'int4out' LANGUAGE internal;
CREATE TYPE myint (INPUT = myintin, OUTPUT = myintout);
CREATE FUNCTION myinteq(myint,myint) RETURNS boolean AS 'int4eq'
LANGUAGE internal;
CREATE FUNCTION myintlt(myint,myint) RETURNS boolean AS 'int4lt'
LANGUAGE internal;
CREATE OPERATOR < (PROCEDURE = myintlt, LEFTARG = myint, RIGHTARG = myint);
CREATE OPERATOR = (PROCEDURE = myinteq, LEFTARG = myint, RIGHTARG = myint);
CREATE FUNCTION btmyintcmp(myint,myint) RETURNS integer AS 'btint4cmp'
LANGUAGE internal;
"""
CREATE_STMT = "CREATE OPERATOR CLASS oc1 FOR TYPE integer USING btree " \
"AS OPERATOR 1 <, OPERATOR 3 =, FUNCTION 1 btint4cmp(integer,integer)"
CREATE_STMT_LONG = "CREATE OPERATOR CLASS sd.oc1 FOR TYPE integer " \
"USING btree AS OPERATOR 1 <(integer,integer), " \
"OPERATOR 3 =(integer,integer), FUNCTION 1 btint4cmp(integer,integer)"
COMMENT_STMT = "COMMENT ON OPERATOR CLASS sd.oc1 USING btree IS " \
"'Test operator class oc1'"
class OperatorClassToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing operator classes"""
superuser = True
def test_map_operclass1(self):
"Map an operator class"
dbmap = self.to_map([CREATE_STMT])
assert dbmap['schema sd']['operator class oc1 using btree'] == \
{'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}}
def test_map_operclass_default(self):
"Map a default operator class"
stmts = [CREATE_TYPE_STMT,
"CREATE OPERATOR CLASS oc1 DEFAULT FOR TYPE sd.myint "
"USING btree AS OPERATOR 1 <, OPERATOR 3 =, "
"FUNCTION 1 btmyintcmp(sd.myint,sd.myint)"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['operator class oc1 using btree'] == \
{'type': 'myint', 'default': True,
'operators': {1: 'sd.<(sd.myint,sd.myint)',
3: 'sd.=(sd.myint,sd.myint)'},
'functions': {1: 'sd.btmyintcmp(sd.myint,sd.myint)'}}
def test_map_operclass_comment(self):
"Map an operator class comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['operator class oc1 using btree'][
'description'] == 'Test operator class oc1'
class OperatorClassToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input operators"""
@pytest.mark.xfail
def test_create_operclass1(self):
"Create an operator class"
inmap = self.std_map()
inmap['schema sd'].update({'operator class oc1 using btree': {
'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT_LONG
def test_create_operclass_default(self):
"Create a default operator class"
inmap = self.std_map()
inmap['schema sd'].update({'operator class oc1 using btree': {
'default': True, 'type': 'myint',
'operators': {1: 'sd.<(sd.myint,sd.myint)',
3: 'sd.=(sd.myint,sd.myint)'},
'functions': {1: 'sd.btmyintcmp(sd.myint,sd.myint)'}}})
sql = self.to_sql(inmap, [CREATE_TYPE_STMT], superuser=True)
assert fix_indent(sql[0]) == "CREATE OPERATOR CLASS sd.oc1 DEFAULT " \
"FOR TYPE sd.myint USING btree AS OPERATOR 1 " \
"sd.<(sd.myint,sd.myint), OPERATOR 3 sd.=(sd.myint,sd.myint), "\
"FUNCTION 1 sd.btmyintcmp(sd.myint,sd.myint)"
@pytest.mark.xfail
def test_create_operclass_in_schema(self):
"Create a operator within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'operator class oc1 using btree': {
'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE OPERATOR CLASS s1.oc1 FOR " \
"TYPE integer USING btree AS OPERATOR 1 <(integer,integer), " \
"OPERATOR 3 =(integer,integer), " \
"FUNCTION 1 btint4cmp(integer,integer)"
def test_drop_operclass(self):
"Drop an existing operator"
sql = self.to_sql(self.std_map(), [CREATE_STMT], superuser=True)
assert sql == ["DROP OPERATOR CLASS sd.oc1 USING btree",
"DROP OPERATOR FAMILY sd.oc1 USING btree"]
@pytest.mark.xfail
def test_operclass_with_comment(self):
"Create an operator class with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'operator class oc1 using btree': {
'description': 'Test operator class oc1', 'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT_LONG
assert sql[1] == COMMENT_STMT
def test_comment_on_operclass(self):
"Create a comment for an existing operator class"
inmap = self.std_map()
inmap['schema sd'].update({'operator class oc1 using btree': {
'description': 'Test operator class oc1', 'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}},
'operator family oc1 using btree': {}})
sql = self.to_sql(inmap, [CREATE_STMT], superuser=True)
assert sql == [COMMENT_STMT]
def test_drop_operclass_comment(self):
"Drop the existing comment on an operator class"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'operator class oc1 using btree': {
'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}},
'operator family oc1 using btree': {}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == ["COMMENT ON OPERATOR CLASS sd.oc1 USING btree IS NULL"]
def test_change_operclass_comment(self):
"Change existing comment on an operator class"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'operator class oc1 using btree': {
'description': 'Changed operator class oc1', 'type': 'integer',
'operators': {1: '<(integer,integer)', 3: '=(integer,integer)'},
'functions': {1: 'btint4cmp(integer,integer)'}},
'operator family oc1 using btree': {}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == ["COMMENT ON OPERATOR CLASS sd.oc1 USING btree IS "
"'Changed operator class oc1'"]
+85
View File
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
"""Test operator families"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE OPERATOR FAMILY sd.of1 USING btree"
COMMENT_STMT = "COMMENT ON OPERATOR FAMILY sd.of1 USING btree IS " \
"'Test operator family of1'"
class OperatorFamilyToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing operators"""
superuser = True
def test_map_operfam(self):
"Map an operator family"
dbmap = self.to_map([CREATE_STMT])
assert dbmap['schema sd']['operator family of1 using btree'] == {}
def test_map_operfam_comment(self):
"Map an operator family comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['operator family of1 using btree'][
'description'] == 'Test operator family of1'
class OperatorFamilyToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input operators"""
def test_create_operfam(self):
"Create an operator family"
inmap = self.std_map()
inmap['schema sd'].update({'operator family of1 using btree': {}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
def test_create_operfam_in_schema(self):
"Create an operator family within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'operator family of1 using btree': {}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == \
"CREATE OPERATOR FAMILY s1.of1 USING btree"
def test_drop_operfam(self):
"Drop an existing operator family"
sql = self.to_sql(self.std_map(), [CREATE_STMT], superuser=True)
assert sql == ["DROP OPERATOR FAMILY sd.of1 USING btree"]
def test_operfam_with_comment(self):
"Create an operator family with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'operator family of1 using btree': {
'description': 'Test operator family of1'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_comment_on_operfam(self):
"Create a comment for an existing operator family"
inmap = self.std_map()
inmap['schema sd'].update({'operator family of1 using btree': {
'description': 'Test operator family of1'}})
sql = self.to_sql(inmap, [CREATE_STMT], superuser=True)
assert sql == [COMMENT_STMT]
def test_drop_operfam_comment(self):
"Drop a comment on an existing operator family"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'operator family of1 using btree': {}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == ["COMMENT ON OPERATOR FAMILY sd.of1 USING btree IS NULL"]
def test_change_operfam_comment(self):
"Change existing comment on a operator"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'operator family of1 using btree': {
'description': 'Changed operator family of1'}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == ["COMMENT ON OPERATOR FAMILY sd.of1 USING btree IS "
"'Changed operator family of1'"]
+117
View File
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""Test object ownership
The majority of other tests exclude owner information. These
explicitly request it.
"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TABLE = "CREATE TABLE sd.t1 (c1 integer, c2 text)"
SOURCE1 = "SELECT 'dummy'::text"
SOURCE2 = "SELECT $1 * $2"
CREATE_FUNC = "CREATE FUNCTION sd.f1() RETURNS text LANGUAGE sql IMMUTABLE " \
"AS $_$%s$_$" % SOURCE1
CREATE_TYPE = "CREATE TYPE sd.t1 AS (x integer, y integer)"
class OwnerToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of object owner information"""
def test_map_type(self):
"Map a composite type"
dbmap = self.to_map([CREATE_TYPE], no_owner=False)
expmap = {'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'integer'}}],
'owner': self.db.user}
assert dbmap['schema sd']['type t1'] == expmap
def test_map_table(self):
"Map a table"
dbmap = self.to_map([CREATE_TABLE], no_owner=False)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'owner': self.db.user}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_function(self):
"Map a function"
dbmap = self.to_map([CREATE_FUNC], no_owner=False)
expmap = {'language': 'sql', 'returns': 'text', 'owner': self.db.user,
'source': SOURCE1, 'volatility': 'immutable'}
assert dbmap['schema sd']['function f1()'] == expmap
class OwnerToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of owner object information"""
def test_create_type(self):
"Create a composite type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'integer'}}],
'owner': self.db.user}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TYPE
assert sql[1] == "ALTER TYPE sd.t1 OWNER TO %s" % self.db.user
def test_create_table(self):
"Create a table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': self.db.user}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE
assert sql[1] == "ALTER TABLE sd.t1 OWNER TO %s" % self.db.user
def test_create_function(self):
"Create a function in a schema and a comment"
inmap = self.std_map()
inmap.update({'schema s1': {'function f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1,
'volatility': 'immutable', 'owner': self.db.user,
'description': 'Test function'}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
# skip first two statements as those are tested elsewhere
assert sql[2] == "ALTER FUNCTION s1.f1() OWNER TO %s" % self.db.user
# to verify correct order of invocation of ownable and commentable
assert sql[3] == "COMMENT ON FUNCTION s1.f1() IS 'Test function'"
def test_create_function_default_args(self):
"Create a function with default arguments"
inmap = self.std_map()
inmap['schema sd'].update({
'function f1(integer, INOUT integer)': {
'allargs': 'integer, INOUT integer DEFAULT 1',
'language': 'sql', 'returns': 'integer', 'source': SOURCE2,
'owner': self.db.user}})
sql = self.to_sql(inmap)
assert fix_indent(sql[1]) == \
"CREATE FUNCTION sd.f1(integer, INOUT integer DEFAULT 1) " \
"RETURNS integer LANGUAGE sql AS $_$%s$_$" % SOURCE2
assert sql[2] == "ALTER FUNCTION sd.f1(integer, INOUT integer) " \
"OWNER TO %s" % self.db.user
def test_change_table_owner(self):
"Change the owner of a table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': 'someuser'}})
sql = self.to_sql(inmap, [CREATE_TABLE])
assert sql[0] == "ALTER TABLE sd.t1 OWNER TO someuser"
def test_change_table_owner_delim(self):
"Change the owner of a table with delimited identifiers"
inmap = self.std_map()
inmap.update({'schema a-schema': {'table a-table': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': 'someuser'}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA \"a-schema\"",
"CREATE TABLE \"a-schema\".\"a-table\" ("
"c1 integer, c2 text)"])
assert sql[0] == "ALTER TABLE \"a-schema\".\"a-table\" OWNER TO " \
"someuser"
+446
View File
@@ -0,0 +1,446 @@
# -*- coding: utf-8 -*-
"""Test object privileges
The majority of other tests exclude access privileges. These
explicitly request it. In addition, the roles 'user1' and 'user2'
are created if they don't exist.
"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase
CREATE_TABLE = "CREATE TABLE t1 (c1 integer, c2 text)"
SOURCE1 = "SELECT 'dummy'::text"
CREATE_FUNC = "CREATE FUNCTION f1() RETURNS text LANGUAGE sql IMMUTABLE AS " \
"$_$%s$_$" % SOURCE1
CREATE_FDW = "CREATE FOREIGN DATA WRAPPER fdw1"
CREATE_FS = "CREATE SERVER fs1 FOREIGN DATA WRAPPER fdw1"
GRANT_SELECT = "GRANT SELECT ON TABLE sd.t1 TO %s"
GRANT_INSUPD = "GRANT INSERT, UPDATE ON TABLE sd.t1 TO %s"
def check_extra_users(db):
"Check existence of extra test users"
for user in ['user1', 'user2']:
row = db.fetchone("SELECT 1 FROM pg_roles WHERE rolname = %s", (user,))
if row is None:
db.execute_commit("CREATE ROLE %s" % user)
class PrivilegeToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of object privilege information"""
def setUp(self):
super(DatabaseToMapTestCase, self).setUp()
check_extra_users(self.db)
def test_map_schema(self):
"Map a schema with some GRANTs"
stmts = ["CREATE SCHEMA s1", "GRANT USAGE ON SCHEMA s1 TO PUBLIC",
"GRANT CREATE, USAGE ON SCHEMA s1 TO user1"]
dbmap = self.to_map(stmts, no_privs=False)
expmap = self.sort_privileges({'privileges': [
{self.db.user: ['all']}, {'PUBLIC': ['usage']},
{'user1': ['all']}]})
assert dbmap['schema s1'] == expmap
def test_map_table(self):
"Map a table with various GRANTs"
stmts = [CREATE_TABLE, GRANT_SELECT % 'PUBLIC', GRANT_INSUPD % 'user1',
"GRANT REFERENCES, TRIGGER ON t1 TO user2 WITH GRANT OPTION"]
dbmap = self.to_map(stmts, no_privs=False)
expmap = self.sort_privileges({'columns': [
{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'privileges': [{self.db.user: ['all']},
{'PUBLIC': ['select']},
{'user1': ['insert', 'update']},
{'user2': [{'trigger': {'grantable': True}},
{'references': {
'grantable': True}}]}]})
assert dbmap['schema sd']['table t1'] == expmap
def test_map_column(self):
"Map a table with GRANTs on column"
self.maxDiff = None
stmts = [CREATE_TABLE, GRANT_SELECT % 'PUBLIC',
"GRANT INSERT (c1, c2) ON t1 TO user1",
"GRANT INSERT (c2), UPDATE (c2) ON t1 TO user2"]
dbmap = self.to_map(stmts, no_privs=False)
expmap = self.sort_privileges({'columns': [
{'c1': {'type': 'integer', 'privileges': [{'user1': ['insert']}]}},
{'c2': {'type': 'text', 'privileges': [
{'user1': ['insert']}, {'user2': [
'insert', 'update']}]}}], 'privileges': [
{self.db.user: ['all']}, {'PUBLIC': ['select']}]})
assert dbmap['schema sd']['table t1'] == expmap
def test_map_sequence(self):
"Map a sequence with various GRANTs"
stmts = ["CREATE SEQUENCE seq1",
"GRANT SELECT ON SEQUENCE seq1 TO PUBLIC",
"GRANT USAGE, UPDATE ON SEQUENCE seq1 TO user1"]
dbmap = self.to_map(stmts, no_privs=False)
expmap = self.sort_privileges({'start_value': 1, 'increment_by': 1,
'max_value': None, 'min_value': None,
'cache_value': 1, 'privileges': [
{self.db.user: ['all']},
{'PUBLIC': ['select']},
{'user1': ['usage', 'update']}]})
assert dbmap['schema sd']['sequence seq1'] == expmap
def test_map_view(self):
"Map a view with various GRANTs"
stmts = ["CREATE VIEW v1 AS SELECT now()::date AS today",
"GRANT SELECT ON v1 TO PUBLIC",
"GRANT REFERENCES ON v1 TO user1"]
dbmap = self.to_map(stmts, no_privs=False)
expmap = self.sort_privileges(
{'columns': [{'today': {'type': 'date'}}],
'definition': " SELECT now()::date AS today;",
'privileges': [{self.db.user: ['all']},
{'PUBLIC': ['select']},
{'user1': ['references']}]})
assert dbmap['schema sd']['view v1'] == expmap
def test_map_function(self):
"Map a function with a GRANT and REVOKE from PUBLIC"
stmts = [CREATE_FUNC, "REVOKE ALL ON FUNCTION f1() FROM PUBLIC",
"GRANT EXECUTE ON FUNCTION f1() TO user1"]
dbmap = self.to_map(stmts, no_privs=False)
expmap = {'language': 'sql', 'returns': 'text',
'source': SOURCE1, 'volatility': 'immutable',
'privileges': [{self.db.user: ['execute']},
{'user1': ['execute']}]}
assert dbmap['schema sd']['function f1()'] == expmap
def test_map_fd_wrapper(self):
"Map a foreign data wrapper with a GRANT"
stmts = [CREATE_FDW,
"GRANT USAGE ON FOREIGN DATA WRAPPER fdw1 TO PUBLIC"]
dbmap = self.to_map(stmts, no_privs=False, superuser=True)
expmap = self.sort_privileges({'privileges':
[{self.db.user: ['usage']},
{'PUBLIC': ['usage']}]})
assert dbmap['foreign data wrapper fdw1'] == expmap
def test_map_server(self):
"Map a foreign server with a GRANT"
stmts = [CREATE_FDW, CREATE_FS,
"GRANT USAGE ON FOREIGN SERVER fs1 TO user1"]
dbmap = self.to_map(stmts, no_privs=False, superuser=True)
expmap = {'privileges': [{self.db.user: ['usage']},
{'user1': ['usage']}]}
assert dbmap['foreign data wrapper fdw1']['server fs1'] == expmap
def test_map_foreign_table(self):
"Map a foreign table with various GRANTs"
stmts = [CREATE_FDW, CREATE_FS,
"CREATE FOREIGN TABLE ft1 (c1 integer, c2 text) SERVER fs1",
"GRANT SELECT ON ft1 TO PUBLIC",
"GRANT INSERT, UPDATE ON ft1 TO user1"]
dbmap = self.to_map(stmts, no_privs=False, superuser=True)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}], 'server': 'fs1',
'privileges': [{self.db.user: ['all']},
{'PUBLIC': ['select']},
{'user1': ['insert', 'update']}]}
assert dbmap['schema sd']['foreign table ft1'] == \
self.sort_privileges(expmap)
class PrivilegeToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of privilege information (GRANTs)"""
def setUp(self):
super(InputMapToSqlTestCase, self).setUp()
check_extra_users(self.db)
def test_create_schema(self):
"Create a schema with various privileges"
inmap = self.std_map()
inmap.update({'schema s1': {
'owner': self.db.user, 'privileges': [{
self.db.user: ['all']}, {'PUBLIC': ['usage', 'create']}]}})
sql = self.to_sql(inmap)
# sql[0] = CREATE SCHEMA
# sql[1] = ALTER SCHEMA OWNER
assert sql[2] == "GRANT ALL ON SCHEMA s1 TO %s" % self.db.user
assert sql[3] == "GRANT ALL ON SCHEMA s1 TO PUBLIC"
def test_schema_new_grant(self):
"Grant privileges on an existing schema"
inmap = self.std_map()
inmap.update({'schema s1': {
'owner': self.db.user, 'privileges': [{self.db.user: ['all']},
{'PUBLIC': ['create']}]}})
sql = sorted(self.to_sql(inmap, ["CREATE SCHEMA s1"]))
assert len(sql) == 2
assert sql[0] == "GRANT ALL ON SCHEMA s1 TO %s" % self.db.user
assert sql[1] == "GRANT CREATE ON SCHEMA s1 TO PUBLIC"
def test_create_table(self):
"Create a table with various privileges"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['select']},
{'user1': ['insert', 'update']},
{'user2': [{'trigger': {'grantable': True}},
{'references': {'grantable': True}}]}]}})
sql = self.to_sql(inmap)
# sql[0] = CREATE TABLE
# sql[1] = ALTER TABLE OWNER
assert sql[2] == "GRANT ALL ON TABLE sd.t1 TO %s" % self.db.user
assert sql[3] == GRANT_SELECT % 'PUBLIC'
assert sql[4] == GRANT_INSUPD % 'user1'
assert sql[5] == "GRANT TRIGGER, REFERENCES ON TABLE sd.t1 " \
"TO user2 WITH GRANT OPTION"
def test_create_column_grants(self):
"Create a table with colum-level privileges"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'privileges': [{'user1': [
'insert']}]}}, {'c2': {'type': 'text', 'privileges': [
{'user1': ['insert']}, {'user2': ['insert', 'update']}]}}],
'owner': self.db.user, 'privileges': [{self.db.user: ['all']},
{'PUBLIC': ['select']}]}})
sql = self.to_sql(inmap)
assert len(sql) == 7
# sql[0] = CREATE TABLE
# sql[1] = ALTER TABLE OWNER
assert sql[2] == "GRANT ALL ON TABLE sd.t1 TO %s" % self.db.user
assert sql[3] == GRANT_SELECT % 'PUBLIC'
assert sql[4] == "GRANT INSERT (c1) ON TABLE sd.t1 TO user1"
assert sql[5] == "GRANT INSERT (c2) ON TABLE sd.t1 TO user1"
assert sql[6] == "GRANT INSERT (c2), UPDATE (c2) ON TABLE sd.t1 " \
"TO user2"
def test_table_new_grant(self):
"Grant select privileges on an existing table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'user1': ['select']}]}})
sql = self.to_sql(inmap, [CREATE_TABLE])
assert len(sql) == 2
sql = sorted(sql)
assert sql[0] == "GRANT ALL ON TABLE sd.t1 TO %s" % self.db.user
assert sql[1] == GRANT_SELECT % 'user1'
def test_table_change_grant(self):
"Grant select privileges on an existing table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['select']},
{'user1': ['insert', 'update']}]}})
sql = self.to_sql(inmap, [CREATE_TABLE, GRANT_SELECT % 'user1'])
assert len(sql) == 3
assert sorted(sql) == [GRANT_INSUPD % 'user1', GRANT_SELECT % 'PUBLIC',
"REVOKE SELECT ON TABLE sd.t1 FROM user1"]
def test_column_change_grants(self):
"Change existing colum-level privileges"
inmap = self.std_map()
inmap['schema sd'].update(
{'table t1': {'columns': [{'c1': {
'type': 'integer', 'privileges': [
{'user1': ['insert']}, {'user2': ['insert', 'update']}]}},
{'c2': {'type': 'text', 'privileges': [
{'user1': ['insert']}]}}],
'owner': self.db.user, 'privileges': [
{self.db.user: ['all']}, {'PUBLIC': ['select']}]}})
stmts = [CREATE_TABLE, GRANT_SELECT % 'PUBLIC',
"GRANT INSERT (c1, c2) ON t1 TO user1",
"GRANT INSERT (c2), UPDATE (c2) ON t1 TO user2"]
sql = self.to_sql(inmap, stmts)
assert len(sql) == 2
assert sql[0] == "GRANT INSERT (c1), UPDATE (c1) ON TABLE sd.t1 " \
"TO user2"
assert sql[1] == "REVOKE INSERT (c2), UPDATE (c2) ON TABLE sd.t1 " \
"FROM user2"
def test_table_revoke_all(self):
"Revoke all privileges on an existing table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'owner': self.db.user}})
stmts = [CREATE_TABLE, GRANT_SELECT % 'PUBLIC', GRANT_INSUPD % 'user1']
sql = sorted(self.to_sql(inmap, stmts))
assert len(sql) == 3
assert sql[0] == "REVOKE ALL ON TABLE sd.t1 FROM %s" % self.db.user
assert sql[1] == "REVOKE INSERT, UPDATE ON TABLE sd.t1 FROM user1"
assert sql[2] == "REVOKE SELECT ON TABLE sd.t1 FROM PUBLIC"
def test_create_sequence(self):
"Create a sequence with some privileges"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1, 'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['select']}]}})
sql = self.to_sql(inmap)
# sql[0] = CREATE SEQUENCE
# sql[1] = ALTER SEQUENCE OWNER
assert sql[2] == "GRANT ALL ON SEQUENCE sd.seq1 TO %s" % self.db.user
assert sql[3] == "GRANT SELECT ON SEQUENCE sd.seq1 TO PUBLIC"
def test_sequence_new_grant(self):
"Grant privileges on an existing sequence"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1, 'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['select']}]}})
sql = sorted(self.to_sql(inmap, ["CREATE SEQUENCE seq1"]))
assert len(sql) == 2
assert sql[0] == "GRANT ALL ON SEQUENCE sd.seq1 TO %s" % self.db.user
assert sql[1] == "GRANT SELECT ON SEQUENCE sd.seq1 TO PUBLIC"
def test_create_view(self):
"Create a view with some privileges"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'definition': " SELECT now()::date AS today;",
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'user1': ['select']}]}})
sql = self.to_sql(inmap)
# sql[0] = CREATE VIEW
# sql[1] = ALTER VIEW OWNER
assert sql[2] == "GRANT ALL ON TABLE sd.v1 TO %s" % self.db.user
assert sql[3] == "GRANT SELECT ON TABLE sd.v1 TO user1"
def test_view_new_grant(self):
"Grant privileges on an existing view"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'today': {'type': 'date'}}],
'definition': " SELECT now()::date AS today;",
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'user1': ['select']}]}})
sql = sorted(self.to_sql(inmap, ["CREATE VIEW v1 AS "
"SELECT now()::date AS today"]))
assert len(sql) == 2
assert sql[0] == "GRANT ALL ON TABLE sd.v1 TO %s" % self.db.user
assert sql[1] == "GRANT SELECT ON TABLE sd.v1 TO user1"
def test_create_function(self):
"Create a function with some privileges"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1,
'volatility': 'immutable', 'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['execute']}]}})
sql = self.to_sql(inmap)
# sql[0:1] = SET, CREATE FUNCTION
# sql[2] = ALTER FUNCTION OWNER
assert sql[3] == "GRANT EXECUTE ON FUNCTION sd.f1() TO %s" % (
self.db.user)
assert sql[4] == "GRANT EXECUTE ON FUNCTION sd.f1() TO PUBLIC"
def test_function_new_grant(self):
"Grant privileges on an existing function"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'sql', 'returns': 'text', 'source': SOURCE1,
'volatility': 'immutable', 'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['execute']}]}})
sql = self.to_sql(inmap, [CREATE_FUNC])
assert len(sql) == 2
sql = sorted(sql)
# assumes self.db.user > PUBLIC
assert sql[0] == "GRANT EXECUTE ON FUNCTION sd.f1() TO PUBLIC"
assert sql[1] == "GRANT EXECUTE ON FUNCTION sd.f1() TO %s" % (
self.db.user)
def test_create_fd_wrapper(self):
"Create a foreign data wrapper with some privileges"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['usage']}]}})
sql = self.to_sql(inmap)
# sql[0] = CREATE FDW
# sql[1] = ALTER FDW OWNER
assert sql[2] == "GRANT USAGE ON FOREIGN DATA WRAPPER fdw1 " \
"TO %s" % self.db.user
assert sql[3] == "GRANT USAGE ON FOREIGN DATA WRAPPER fdw1 TO PUBLIC"
def test_fd_wrapper_new_grant(self):
"Grant privileges on an existing foreign data wrapper"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['usage']}]}})
sql = sorted(self.to_sql(inmap, [CREATE_FDW], superuser=True))
assert len(sql) == 2
# assumes self.db.user > PUBLIC
assert sql[0] == "GRANT USAGE ON FOREIGN DATA WRAPPER fdw1 TO PUBLIC"
assert sql[1] == "GRANT USAGE ON FOREIGN DATA WRAPPER fdw1 " \
"TO %s" % self.db.user
def test_create_server(self):
"Create a foreign server with some privileges"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'user2': ['usage']}]}}})
sql = self.to_sql(inmap, [CREATE_FDW], superuser=True)
# sql[0] = CREATE SERVER
# sql[1] = ALTER SERVER OWNER
assert sql[2] == "GRANT USAGE ON FOREIGN SERVER fs1 TO %s" % \
self.db.user
assert sql[3] == "GRANT USAGE ON FOREIGN SERVER fs1 TO user2"
def test_server_new_grant(self):
"Grant privileges on an existing foreign server"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'user2': ['usage']}]}}})
sql = sorted(self.to_sql(inmap, [CREATE_FDW, CREATE_FS],
superuser=True))
assert len(sql) == 2
assert sql[0] == "GRANT USAGE ON FOREIGN SERVER fs1 TO %s" % \
self.db.user
assert sql[1] == "GRANT USAGE ON FOREIGN SERVER fs1 TO user2"
def test_create_foreign_table(self):
"Create a foreign table with some privileges"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'foreign table ft1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}], 'server': 'fs1',
'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['select']},
{'user1': ['insert', 'update']}]}})
sql = self.to_sql(inmap, [CREATE_FDW, CREATE_FS], superuser=True)
# sql[0] = CREATE TABLE
# sql[1] = ALTER TABLE OWNER
assert sql[2] == "GRANT ALL ON TABLE sd.ft1 TO %s" % self.db.user
assert sql[3] == "GRANT SELECT ON TABLE sd.ft1 TO PUBLIC"
assert sql[4] == "GRANT INSERT, UPDATE ON TABLE sd.ft1 TO user1"
def test_foreign_table_new_grant(self):
"Grant privileges on an existing foreign table"
inmap = self.std_map()
inmap.update({'foreign data wrapper fdw1': {'server fs1': {}}})
inmap['schema sd'].update({'foreign table ft1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'server': 'fs1', 'owner': self.db.user,
'privileges': [{self.db.user: ['all']}, {'PUBLIC': ['select']},
{'user1': ['insert', 'update']}]}})
sql = sorted(self.to_sql(inmap, [
CREATE_FDW, CREATE_FS,
"CREATE FOREIGN TABLE ft1 (c1 integer, c2 text) SERVER fs1"],
superuser=True))
assert len(sql) == 3
assert sql[0] == "GRANT ALL ON TABLE sd.ft1 TO %s" % self.db.user
assert sql[1] == "GRANT INSERT, UPDATE ON TABLE sd.ft1 TO user1"
assert sql[2] == "GRANT SELECT ON TABLE sd.ft1 TO PUBLIC"
+191
View File
@@ -0,0 +1,191 @@
# -*- coding: utf-8 -*-
"""Test rules"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TABLE_STMT = "CREATE TABLE sd.t1 (c1 integer, c2 text)"
CREATE_STMT = "CREATE RULE r1 AS ON %s TO sd.t1 DO %s"
COMMENT_STMT = "COMMENT ON RULE r1 ON sd.t1 IS 'Test rule r1'"
class RuleToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing rules"""
def test_map_rule_nothing(self):
"Map a rule to do nothing"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING')]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['rules'] == \
{'r1': {'event': 'insert', 'actions': 'NOTHING'}}
def test_map_rule_instead(self):
"Map rule with an INSTEAD action"
stmts = [CREATE_TABLE_STMT,
CREATE_STMT % ('UPDATE', 'INSTEAD NOTHING')]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['rules'] == \
{'r1': {'event': 'update', 'instead': True, 'actions': 'NOTHING'}}
def test_map_rule_conditional(self):
"Map rule with a qualification"
stmts = [CREATE_TABLE_STMT,
"CREATE RULE r1 AS ON DELETE TO t1 WHERE OLD.c1 < 1000 "
"DO INSERT INTO t1 VALUES (OLD.c1 + 1000, OLD.c2)"]
dbmap = self.to_map(stmts)
fmt = " %s\n %s"
action = fmt % ("INSERT INTO sd.t1 (c1, c2)",
" VALUES ((old.c1 + 1000), old.c2)")
expmap = {'r1': {'event': 'delete', 'condition': "(old.c1 < 1000)",
'actions': action}}
assert dbmap['schema sd']['table t1']['rules'] == expmap
def test_map_rule_multi_actions(self):
"Map rule with multiple actions"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % (
'UPDATE', "(INSERT INTO t1 (c1) VALUES (old.c1 + 100); "
"INSERT INTO t1 (c1) VALUES (old.c1 + 200))")]
dbmap = self.to_map(stmts)
ins = "INSERT INTO sd.t1 (c1)"
fmt = "( %s\n %s\n %s\n %s\n)"
actions = fmt % (ins, " VALUES ((old.c1 + 100));",
ins, " VALUES ((old.c1 + 200));")
expmap = {'r1': {'event': 'update', 'actions': actions}}
assert dbmap['schema sd']['table t1']['rules'] == expmap
def test_map_rule_comment(self):
"Map a rule comment"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING'),
COMMENT_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['rules']['r1'][
'description'] == 'Test rule r1'
class RuleToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input rules"""
def test_create_rule_nothing(self):
"Create a rule"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'insert', 'actions': 'NOTHING'}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE_STMT
assert fix_indent(sql[1]) == CREATE_STMT % ('INSERT', 'NOTHING')
def test_create_rule_instead(self):
"Create a rule with an INSTEAD action"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'update', 'instead': True,
'actions': 'NOTHING'}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE_STMT
assert fix_indent(sql[1]) == "CREATE RULE r1 AS ON UPDATE TO sd.t1 " \
"DO INSTEAD NOTHING"
def test_create_rule_conditional(self):
"Create a rule with qualification"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'delete',
'condition': "old.c1 < 1000",
'actions': "INSERT INTO t1 VALUES ("
"old.c1 + 1000, old.c2)"}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE_STMT
assert fix_indent(sql[1]) == "CREATE RULE r1 AS ON DELETE TO sd.t1 " \
"WHERE old.c1 < 1000 DO INSERT INTO t1 VALUES " \
"(old.c1 + 1000, old.c2)"
def test_create_rule_multi_actions(self):
"Create a rule with multiple actions"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'update', 'actions':
"(INSERT INTO t1 VALUES (old.c1 + 100); "
"INSERT INTO t1 VALUES (old.c1 + 200));)"}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE_STMT
assert fix_indent(sql[1]) == "CREATE RULE r1 AS ON UPDATE " \
"TO sd.t1 DO (INSERT INTO t1 VALUES (old.c1 + 100); " \
"INSERT INTO t1 VALUES (old.c1 + 200));)"
def test_create_rule_in_schema(self):
"Create a rule within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {
'table t1': {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'insert',
'actions': 'NOTHING'}}}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[1]) == "CREATE RULE r1 AS ON INSERT TO s1.t1 " \
"DO NOTHING"
def test_drop_rule(self):
"Drop an existing rule"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING')]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
sql = self.to_sql(inmap, stmts)
assert sql == ["DROP RULE r1 ON sd.t1"]
def test_drop_rule_table(self):
"Drop an existing rule and the related table"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING')]
sql = self.to_sql(self.std_map(), stmts)
assert sql[0] == "DROP RULE r1 ON sd.t1"
assert sql[1] == "DROP TABLE sd.t1"
def test_rule_with_comment(self):
"Create a rule with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'insert', 'description': 'Test rule r1',
'actions': 'NOTHING'}}}})
sql = self.to_sql(inmap)
assert sql[2] == COMMENT_STMT
def test_comment_on_rule(self):
"Create a comment on an existing rule"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING')]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'insert', 'description': 'Test rule r1',
'actions': 'NOTHING'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == [COMMENT_STMT]
def test_drop_rule_comment(self):
"Drop the comment on an existing rule"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING'),
COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'insert', 'actions': 'NOTHING'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON RULE r1 ON sd.t1 IS NULL"]
def test_change_rule_comment(self):
"Change existing comment on a rule"
stmts = [CREATE_TABLE_STMT, CREATE_STMT % ('INSERT', 'NOTHING'),
COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'rules': {'r1': {'event': 'insert',
'description': 'Changed rule r1',
'actions': 'NOTHING'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON RULE r1 ON sd.t1 IS 'Changed rule r1'"]
+134
View File
@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
"""Test schemas"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase
CREATE_STMT = "CREATE SCHEMA s1"
COMMENT_STMT = "COMMENT ON SCHEMA s1 IS 'Test schema s1'"
class SchemaToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created schemas"""
def test_map_schema(self):
"Map a created schema"
dbmap = self.to_map([CREATE_STMT])
assert dbmap['schema s1'] == {}
def test_map_schema_comment(self):
"Map a schema comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema s1'] == {'description': 'Test schema s1'}
def test_map_select_schema(self):
"Map a single schema when three schemas exist"
stmts = [CREATE_STMT, "CREATE SCHEMA s2", "CREATE SCHEMA s3"]
dbmap = self.to_map(stmts, schemas=['s2'])
assert 'schema s1' not in dbmap
assert dbmap['schema s2'] == {}
assert 'schema s3' not in dbmap
class SchemaToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input schemas"""
def base_schmap(self):
return {'schema s1': {'description': 'Test schema s1'}}
def test_create_schema(self):
"Create a new schema"
inmap = self.std_map()
inmap.update({'schema s1': {}})
sql = self.to_sql(inmap)
assert sql == [CREATE_STMT]
def test_bad_schema_map(self):
"Error creating a schema with a bad map"
with pytest.raises(KeyError):
self.to_sql({'s1': {}})
def test_drop_schema(self):
"Drop an existing schema"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql == ["DROP SCHEMA s1"]
def test_rename_schema(self):
"Rename an existing schema"
inmap = self.std_map()
inmap.update({'schema s2': {'oldname': 's1'}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == ["ALTER SCHEMA s1 RENAME TO s2"]
def test_bad_rename_schema(self):
"Error renaming a non-existing schema"
inmap = self.std_map()
inmap.update({'schema s2': {'oldname': 's3'}})
with pytest.raises(KeyError):
self.to_sql(inmap, [CREATE_STMT])
def test_schema_with_comment(self):
"Create a schema with a comment"
inmap = self.std_map()
inmap.update(self.base_schmap())
sql = self.to_sql(inmap)
assert sql == [CREATE_STMT, COMMENT_STMT]
def test_comment_on_schema(self):
"Create a comment for an existing schema"
inmap = self.std_map()
inmap.update(self.base_schmap())
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == [COMMENT_STMT]
def test_drop_schema_comment(self):
"Drop a comment on an existing schema"
inmap = self.std_map()
inmap.update({'schema s1': {}})
stmts = [CREATE_STMT, COMMENT_STMT]
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON SCHEMA s1 IS NULL"]
def test_change_schema_comment(self):
"Change existing comment on a schema"
inmap = self.std_map()
inmap.update({'schema s1': {'description': 'Changed schema s1'}})
stmts = [CREATE_STMT, COMMENT_STMT]
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON SCHEMA s1 IS 'Changed schema s1'"]
class SchemaUndoSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation to revert schemas"""
def base_schmap(self):
return {'schema s1': {'description': 'Test schema s1'}}
def test_undo_create_schema(self):
"Revert a schema creation"
inmap = self.std_map()
inmap.update({'schema s1': {}})
sql = self.to_sql(inmap, revert=True)
assert sql == ["DROP SCHEMA s1"]
def test_undo_drop_schema(self):
"Revert dropping a schema"
sql = self.to_sql(self.std_map(), [CREATE_STMT], revert=True)
assert sql[0] == CREATE_STMT
def test_undo_comment_on_schema(self):
"Revert creating comment on a schema"
inmap = self.std_map()
inmap.update(self.base_schmap())
sql = self.to_sql(inmap, [CREATE_STMT], revert=True)
assert sql == ["COMMENT ON SCHEMA s1 IS NULL"]
def test_undo_drop_schema_comment(self):
"Revert dropping comment on a schema"
inmap = self.std_map()
inmap.update({'schema s1': {}})
stmts = [CREATE_STMT, COMMENT_STMT]
sql = self.to_sql(inmap, stmts, revert=True)
assert sql == [COMMENT_STMT]
+180
View File
@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
"""Test sequences"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE SEQUENCE seq1"
CREATE_STMT_FULL = "CREATE SEQUENCE sd.seq1 %sSTART WITH 1 INCREMENT BY 1 " \
"NO MINVALUE NO MAXVALUE CACHE 1"
COMMENT_STMT = "COMMENT ON SEQUENCE sd.seq1 IS 'Test sequence seq1'"
class SequenceToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created sequences"""
def test_map_sequence_simple(self):
"Map a created sequence"
dbmap = self.to_map([CREATE_STMT])
expmap = {'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1}
assert dbmap['schema sd']['sequence seq1'] == expmap
def test_map_sequence_comment(self):
"Map a sequence with a comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['sequence seq1']['description'] == \
'Test sequence seq1'
class SequenceToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input sequences"""
def test_create_sequence_simple(self):
"Create a sequence"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1, 'data_type': 'integer'}})
sql = self.to_sql(inmap)
mod = 'AS integer '
assert fix_indent(sql[0]) == CREATE_STMT_FULL % mod
def test_create_sequence_in_schema(self):
"Create a sequence within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE SEQUENCE s1.seq1 START WITH 1 " \
"INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1"
def test_bad_sequence_map(self):
"Error creating a sequence with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_sequence(self):
"Drop an existing sequence"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql == ["DROP SEQUENCE sd.seq1"]
def test_no_drop_owned_sequence(self):
"Don't drop a sequence owned by a table column"
sql = self.to_sql(self.std_map(),
["CREATE TABLE t1 (c1 SERIAL, c2 text)"])
assert sql == ["DROP TABLE sd.t1"]
def test_rename_sequence(self):
"Rename an existing sequence"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq2': {
'oldname': 'seq1', 'start_value': 1, 'increment_by': 1,
'max_value': None, 'min_value': None, 'cache_value': 1}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == ["ALTER SEQUENCE sd.seq1 RENAME TO seq2"]
def test_bad_rename_sequence(self):
"Error renaming a non-existing sequence"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq2': {
'oldname': 'seq3', 'start_value': 1, 'increment_by': 1,
'max_value': None, 'min_value': None, 'cache_value': 1}})
with pytest.raises(KeyError):
self.to_sql(inmap, [CREATE_STMT])
def test_change_sequence(self):
"Change sequence attributes"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 5, 'increment_by': 10, 'max_value': None,
'min_value': None, 'cache_value': 30}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert fix_indent(sql[0]) == "ALTER SEQUENCE sd.seq1 START WITH 5 " \
"INCREMENT BY 10 CACHE 30"
def test_sequence_with_comment(self):
"Create a sequence with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1,
'description': "Test sequence seq1"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT_FULL % ''
assert sql[1] == COMMENT_STMT
def test_comment_on_sequence(self):
"Create a comment for an existing sequence"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1,
'description': "Test sequence seq1"}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == [COMMENT_STMT]
def test_drop_sequence_comment(self):
"Drop the comment on an existing sequence"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON SEQUENCE sd.seq1 IS NULL"]
def test_change_sequence_comment(self):
"Change existing comment on a sequence"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1,
'description': "Changed sequence seq1"}})
sql = self.to_sql(inmap, stmts)
assert sql, ["COMMENT ON SEQUENCE sd.seq1 IS 'Changed sequence seq1'"]
class SequenceUndoSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation to revert sequences"""
def test_undo_create_sequence(self):
"Revert a sequence creation"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1}})
sql = self.to_sql(inmap, revert=True)
assert sql == ["DROP SEQUENCE sd.seq1"]
def test_undo_create_sequence_in_schema(self):
"Revert creating a sequence in a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'sequence seq1': {
'start_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'cache_value': 1}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"], revert=True)
assert sql == ["DROP SEQUENCE s1.seq1"]
def test_undo_drop_sequence(self):
"Revert dropping a sequence"
sql = self.to_sql(self.std_map(), [CREATE_STMT], revert=True)
assert fix_indent(sql[0]) == CREATE_STMT_FULL % ''
def test_undo_change_sequence(self):
"Revert changing sequence attributes"
inmap = self.std_map()
inmap['schema sd'].update({'sequence seq1': {
'start_value': 5, 'increment_by': 10, 'max_value': None,
'min_value': None, 'cache_value': 30}})
sql = self.to_sql(inmap, [CREATE_STMT], revert=True)
assert fix_indent(sql[0]) == "ALTER SEQUENCE sd.seq1 START WITH 1 " \
"INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1"
+103
View File
@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
"""Test loading of data from and into static tables"""
import os
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase
CREATE_STMT = "CREATE TABLE t1 (c1 integer, c2 text)"
FILE_PATH = 'table.t1.data'
TABLE_DATA = [(1, 'abc'), (2, 'def'), (3, 'ghi')]
TABLE_DATA2 = [(1, 'abc', 'row 1'), (3, 'ghi', 'row 2'), (2, 'def', 'row 3'),
(3, 'def', 'row 4')]
class StaticTableToMapTestCase(DatabaseToMapTestCase):
"""Test mapping and copying out of created tables"""
def tearDown(self):
self.remove_tempfiles()
def test_copy_static_table(self):
"Copy a two-column table to a file"
self.db.execute(CREATE_STMT)
for row in TABLE_DATA:
self.db.execute("INSERT INTO t1 VALUES (%s, %s)", row)
cfg = {'datacopy': {'schema sd': ['t1']}}
dbmap = self.to_map([], config=cfg)
assert dbmap['schema sd']['table t1'] == {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}]}
recs = []
with open(os.path.join(self.cfg['files']['data_path'],
"schema.sd", FILE_PATH)) as f:
for line in f:
(c1, c2) = line.split(',')
recs.append((int(c1), c2.rstrip()))
assert recs == TABLE_DATA
def test_copy_static_table_pk(self):
"Copy a table that has a primary key"
self.db.execute("CREATE TABLE t1 (c1 integer, c2 char(3), c3 text,"
"PRIMARY KEY (c2, c1))")
for row in TABLE_DATA2:
self.db.execute("INSERT INTO t1 VALUES (%s, %s, %s)", row)
cfg = {'datacopy': {'schema sd': ['t1']}}
dbmap = self.to_map([], config=cfg)
assert dbmap['schema sd']['table t1'] == {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'character(3)', 'not_null': True}},
{'c3': {'type': 'text'}}],
'primary_key': {'t1_pkey': {'columns': ['c2', 'c1']}}}
recs = []
with open(os.path.join(self.cfg['files']['data_path'],
"schema.sd", FILE_PATH)) as f:
for line in f:
(c1, c2, c3) = line.split(',')
recs.append((int(c1), c2, c3.rstrip()))
assert recs == sorted(TABLE_DATA2)
class StaticTableToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of data import statements"""
def test_load_static_table(self):
"Truncate and import a two-column table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
cfg = {'datacopy': {'schema sd': ['t1']}}
sql = self.to_sql(inmap, [CREATE_STMT], config=cfg)
copy_stmt = ("\\copy ", 'sd.t1', " from '",
os.path.join(self.cfg['files']['data_path'],
"schema.sd", FILE_PATH), "' csv")
assert sql[0] == "TRUNCATE ONLY sd.t1"
assert sql[1] == copy_stmt
def test_load_static_table_fk(self):
"Truncate and import a table which has a foreign key dependency"
stmts = ["CREATE TABLE t1 (pc1 integer PRIMARY KEY, pc2 text)",
"CREATE TABLE t2 (c1 integer, c2 integer REFERENCES t1, "
"c3 text)"]
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'pc1': {'type': 'integer', 'not_null': True}},
{'pc2': {'type': 'text'}}],
'primary_key': {'t1_pkey': {'columns': ['pc1']}}}, 'table t2': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'integer'}},
{'c3': {'type': 'text'}}],
'foreign_keys': {'t2_c2_fkey': {
'columns': ['c2'],
'references': {'schema': 'sd', 'table': 't1',
'columns': ['pc1']}}}}})
cfg = {'datacopy': {'schema sd': ['t1']}}
sql = self.to_sql(inmap, stmts, config=cfg)
copy_stmt = ("\\copy ", 'sd.t1', " from '",
os.path.join(self.cfg['files']['data_path'],
"schema.sd", FILE_PATH), "' csv")
assert sql[0] == "ALTER TABLE sd.t2 DROP CONSTRAINT t2_c2_fkey"
assert sql[1] == "TRUNCATE ONLY sd.t1"
assert sql[2] == copy_stmt
assert sql[3] == "ALTER TABLE sd.t2 ADD CONSTRAINT t2_c2_fkey " \
"FOREIGN KEY (c2) REFERENCES sd.t1 (pc1)"
+436
View File
@@ -0,0 +1,436 @@
# -*- coding: utf-8 -*-
"""Test tables"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE TABLE sd.t1 (c1 integer, c2 text)"
COMMENT_STMT = "COMMENT ON TABLE sd.t1 IS 'Test table t1'"
CREATE_STOR_PARAMS = CREATE_STMT + \
" WITH (fillfactor=90, autovacuum_enabled=false)"
class TableToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created tables"""
def test_map_table_simple(self):
"Map a table with two columns"
dbmap = self.to_map([CREATE_STMT])
assert dbmap['schema sd']['table t1'] == {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}]}
def test_map_table_comment(self):
"Map a table comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['table t1'] == {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'description': 'Test table t1'}
def test_map_table_comment_quotes(self):
"Map a table comment with quotes"
stmts = [CREATE_STMT, "COMMENT ON TABLE t1 IS "
"'A \"special\" person''s table t1'"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1'] == {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'description': "A \"special\" person's table t1"}
def test_map_column_comments(self):
"Map two column comments"
stmts = [CREATE_STMT,
"COMMENT ON COLUMN t1.c1 IS 'Test column c1 of t1'",
"COMMENT ON COLUMN t1.c2 IS 'Test column c2 of t1'"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1'] == {
'columns': [{'c1': {'type': 'integer',
'description': 'Test column c1 of t1'}},
{'c2': {'type': 'text',
'description': 'Test column c2 of t1'}}]}
def test_map_table_options(self):
"Map a table with options"
dbmap = self.to_map([CREATE_STOR_PARAMS])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'options': ["fillfactor=90", 'autovacuum_enabled=false']}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_inherit(self):
"Map a table that inherits from two other tables"
stmts = [CREATE_STMT, "CREATE TABLE t2 (c3 integer)",
"CREATE TABLE t3 (c4 text) INHERITS (t1, t2)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer', 'inherited': True}},
{'c2': {'type': 'text', 'inherited': True}},
{'c3': {'type': 'integer', 'inherited': True}},
{'c4': {'type': 'text'}}],
'inherits': ['t1', 't2']}
assert dbmap['schema sd']['table t3'] == expmap
def test_map_inherit_delim(self):
"Map a table that inherits from two other tables (delim identifiers)"
stmts = [CREATE_STMT, "CREATE TABLE \"t-2\" (c3 integer)",
"CREATE TABLE \"t-3\" (c4 text) INHERITS (t1, \"t-2\")"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer', 'inherited': True}},
{'c2': {'type': 'text', 'inherited': True}},
{'c3': {'type': 'integer', 'inherited': True}},
{'c4': {'type': 'text'}}],
"inherits": ["t1", "t-2"]}
assert dbmap["schema sd"]["table t-3"] == expmap
def test_map_unlogged_table(self):
"Map an unlogged table"
dbmap = self.to_map(["CREATE UNLOGGED TABLE t1 (c1 integer, c2 text)"])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'unlogged': True}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_table_within_schema(self):
"Map a schema and a table within it"
stmts = ["CREATE SCHEMA s1",
"CREATE TABLE s1.t1 (c1 INTEGER, c2 TEXT)"]
dbmap = self.to_map(stmts)
assert dbmap['schema s1'] == {
'table t1': {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}}
def test_map_table_quoted(self):
"Map a schema and a table both of which need to be quoted"
stmts = ['CREATE SCHEMA "a schema"',
'CREATE TABLE "a schema"."The.Table" ("column" SERIAL, '
'c2 TEXT)']
dbmap = self.to_map(stmts)
assert dbmap['schema a schema']['table The.Table'] == {
'columns': [{'column': {
'type': 'integer', 'not_null': True,
'default':
'nextval(\'"a schema"."The.Table_column_seq"\'::regclass)'}},
{'c2': {'type': 'text'}}]}
def test_map_select_tables(self):
"Map two tables out of three present"
stmts = [CREATE_STMT, "CREATE TABLE t2 (c1 integer, c2 text)",
"CREATE TABLE t3 (c1 integer, c2 text)"]
dbmap = self.to_map(stmts, tables=['t2', 't1'])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}
assert dbmap['schema sd']['table t1'] == expmap
assert dbmap['schema sd']['table t2'] == expmap
assert 'table t3' not in dbmap['schema sd']
def test_map_table_sequence(self):
"Map sequence if owned by a table"
stmts = [CREATE_STMT, "CREATE TABLE t2 (c1 integer, c2 text)",
"CREATE SEQUENCE seq1", "ALTER SEQUENCE seq1 OWNED BY t2.c1",
"CREATE SEQUENCE seq2"]
dbmap = self.to_map(stmts, tables=['t2'])
self.db.execute_commit("DROP SEQUENCE seq1")
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}
assert 'table t1' not in dbmap['schema sd']
assert dbmap['schema sd']['table t2'] == expmap
assert 'sequence seq1' in dbmap['schema sd']
assert 'sequence seq2' not in dbmap['schema sd']
def test_map_partition_range(self):
"Map a partitioned table with two partitions by range"
spec1 = "FROM ('2015-01-01', MINVALUE) TO ('2016-12-31', 5)"
stmts = ["CREATE TABLE t1 (c1 date, c2 integer, c3 text) "
"PARTITION BY RANGE (c1, c2)",
"CREATE TABLE t1a PARTITION OF t1 FOR VALUES %s" % spec1,
"CREATE TABLE t1b PARTITION OF t1 FOR VALUES "
"FROM ('2017-01-01', 11) TO ('2020-12-31', 15)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'date'}},
{'c2': {'type': 'integer'}},
{'c3': {'type': 'text'}}],
'partition_by': {'range': ['c1', 'c2']}}
assert dbmap['schema sd']['table t1'] == expmap
expmap2 = {'partition_bound_spec': spec1, 'partition_of': 't1'}
assert dbmap['schema sd']['table t1a'] == expmap2
def test_map_partition_list(self):
"Map a partitioned table with two partitions by list"
spec1 = "IN (1, 3, 5, 7)"
stmts = ["CREATE TABLE t1 (c1 integer, c2 text) "
"PARTITION BY LIST (c1)",
"CREATE TABLE t1a PARTITION OF t1 FOR VALUES %s" % spec1,
"CREATE TABLE t1b PARTITION OF t1 FOR VALUES IN (2, 4, 6)"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'partition_by': {'list': ['c1']}}
assert dbmap['schema sd']['table t1'] == expmap
expmap2 = {'partition_bound_spec': spec1, 'partition_of': 't1'}
assert dbmap['schema sd']['table t1a'] == expmap2
class TableToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of table statements from input schemas"""
def test_create_table_simple(self):
"Create a two-column table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
def test_create_table_quoted_idents(self):
"Create a table needing quoted identifiers"
inmap = self.std_map()
inmap['schema sd'].update({'table order': {
'columns': [{'primary': {'type': 'integer'}},
{'two words': {'type': 'text'}}]}})
sql = self.to_sql(inmap, quote_reserved=True)
assert fix_indent(sql[0]) == 'CREATE TABLE sd."order" (' \
'"primary" integer, "two words" text)'
def test_bad_table_map(self):
"Error creating a table with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_missing_columns(self):
"Error creating a table with no columns"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {'columns': []}})
with pytest.raises(ValueError):
self.to_sql(inmap)
def test_drop_table(self):
"Drop an existing table"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql == ["DROP TABLE sd.t1"]
def test_rename_table(self):
"Rename an existing table"
inmap = self.std_map()
inmap['schema sd'].update({'table t2': {
'oldname': 't1',
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == ["ALTER TABLE sd.t1 RENAME TO t2"]
def test_create_table_options(self):
"Create a table with options"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'options': ["fillfactor=90", "autovacuum_enabled=false"]}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STOR_PARAMS
def test_change_table_options(self):
"Change a table's storage parameters"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'options': ["fillfactor=70"]}})
sql = self.to_sql(inmap, [CREATE_STOR_PARAMS])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 SET (fillfactor=70)," \
" RESET (autovacuum_enabled)"
def test_create_table_within_schema(self):
"Create a new schema and a table within it"
inmap = self.std_map()
inmap.update({'schema s1': {'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}}})
sql = self.to_sql(inmap)
expsql = ["CREATE SCHEMA s1",
"CREATE TABLE s1.t1 (c1 integer, c2 text)"]
for i in range(len(expsql)):
assert fix_indent(sql[i]) == expsql[i]
def test_unlogged_table(self):
"Create an unlogged table"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}], 'unlogged': True}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == \
"CREATE UNLOGGED TABLE sd.t1 (c1 integer, c2 text)"
def test_table_owned_by_sequence(self):
"Alter a table to be owned by a table column"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]},
'sequence seq1': {
'cache_value': 1, 'increment_by': 1, 'max_value': None,
'min_value': None, 'start_value': 1,
'owner_table': 't1', 'owner_column': 'c1'}})
sql = self.to_sql(inmap, [CREATE_STMT, "CREATE SEQUENCE seq1"])
assert sql[0] == "ALTER SEQUENCE sd.seq1 OWNED BY sd.t1.c1"
def test_create_partitioned_tables(self):
"Create a partitioned table and two partitions"
inmap = self.std_map()
spec1 = "FROM ('2015-01-01', MINVALUE) TO ('2016-12-31', 5)"
spec2 = "FROM ('2017-01-01', 11) TO ('2020-12-31', 15)"
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'date'}}, {'c2': {'type': 'integer'}},
{'c3': {'type': 'text'}}],
'partition_by': {'range': ['c1', 'c2']}}, 'table t1a': {
'partition_bound_spec': spec1, 'partition_of': 't1'},
'table t1b': {'partition_bound_spec': spec2,
'partition_of': 't1'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TABLE sd.t1 (c1 date, " \
"c2 integer, c3 text) PARTITION BY RANGE (c1, c2)"
assert fix_indent(sql[1]) == (
"CREATE TABLE sd.t1a PARTITION OF t1 FOR VALUES %s" % spec1)
assert fix_indent(sql[2]) == (
"CREATE TABLE sd.t1b PARTITION OF t1 FOR VALUES %s" % spec2)
class TableCommentToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of table and column COMMENT statements"""
def _tblmap(self):
"Return a table input map with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'description': 'Test table t1',
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
return inmap
def test_table_with_comment(self):
"Create a table with a comment"
sql = self.to_sql(self._tblmap())
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_comment_on_table(self):
"Create a comment for an existing table"
sql = self.to_sql(self._tblmap(), [CREATE_STMT])
assert sql == [COMMENT_STMT]
def test_table_comment_quotes(self):
"Create a table comment with quotes"
inmap = self._tblmap()
inmap['schema sd']['table t1']['description'] = \
"A \"special\" person's table t1"
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == ["COMMENT ON TABLE sd.t1 IS "
"'A \"special\" person''s table t1'"]
def test_drop_table_comment(self):
"Drop a comment on an existing table"
inmap = self._tblmap()
del inmap['schema sd']['table t1']['description']
sql = self.to_sql(inmap, [CREATE_STMT, COMMENT_STMT])
assert sql == ["COMMENT ON TABLE sd.t1 IS NULL"]
def test_change_table_comment(self):
"Change existing comment on a table"
inmap = self._tblmap()
inmap['schema sd']['table t1'].update(
{'description': 'Changed table t1'})
sql = self.to_sql(inmap, [CREATE_STMT, COMMENT_STMT])
assert sql == ["COMMENT ON TABLE sd.t1 IS 'Changed table t1'"]
def test_create_column_comments(self):
"Create a table with column comments"
inmap = self._tblmap()
inmap['schema sd']['table t1']['columns'][0]['c1'].update(
description='Test column c1')
inmap['schema sd']['table t1']['columns'][1]['c2'].update(
description='Test column c2')
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
assert sql[2] == "COMMENT ON COLUMN sd.t1.c1 IS 'Test column c1'"
assert sql[3] == "COMMENT ON COLUMN sd.t1.c2 IS 'Test column c2'"
def test_add_column_comment(self):
"Add a column comment to an existing table"
inmap = self._tblmap()
inmap['schema sd']['table t1']['columns'][0]['c1'].update(
description='Test column c1')
sql = self.to_sql(inmap, [CREATE_STMT, COMMENT_STMT])
assert sql[0] == "COMMENT ON COLUMN sd.t1.c1 IS 'Test column c1'"
def test_add_column_with_comment(self):
"Add a commented column to an existing table"
inmap = self._tblmap()
inmap['schema sd']['table t1']['columns'].append({'c3': {
'description': 'Test column c3', 'type': 'integer'}})
sql = self.to_sql(inmap, [CREATE_STMT, COMMENT_STMT])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 ADD COLUMN c3 integer"
assert sql[1] == "COMMENT ON COLUMN sd.t1.c3 IS 'Test column c3'"
def test_drop_column_comment(self):
"Drop a column comment on an existing table"
stmts = [CREATE_STMT, COMMENT_STMT,
"COMMENT ON COLUMN t1.c1 IS 'Test column c1'"]
sql = self.to_sql(self._tblmap(), stmts)
assert sql[0] == "COMMENT ON COLUMN sd.t1.c1 IS NULL"
def test_change_column_comment(self):
"Add a column comment to an existing table"
inmap = self._tblmap()
inmap['schema sd']['table t1']['columns'][0]['c1'].update(
description='Changed column c1')
sql = self.to_sql(inmap, [CREATE_STMT, COMMENT_STMT])
assert sql[0] == "COMMENT ON COLUMN sd.t1.c1 IS 'Changed column c1'"
class TableInheritToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of table inheritance statements"""
def test_table_inheritance(self):
"Create a table that inherits from another"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}})
inmap['schema sd'].update({'table t2': {
'columns': [{'c1': {'type': 'integer', 'inherited': True}},
{'c2': {'type': 'text', 'inherited': True}},
{'c3': {'type': 'numeric'}}], 'inherits': ['t1']}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert fix_indent(sql[1]) == "CREATE TABLE sd.t2 (c3 numeric) " \
"INHERITS (sd.t1)"
def test_table_inherit_delim(self):
"Create a table that inherits from another (delimited identifiers)"
inmap = self.std_map()
inmap.update({'schema s-d': {'table t-1': {
'columns': [{'c-1': {'type': 'integer'}},
{'c2': {'type': 'text'}}]}}})
inmap['schema s-d'].update({'table t-2': {
'columns': [{'c-1': {'type': 'integer', 'inherited': True}},
{'c2': {'type': 'text', 'inherited': True}},
{'c3': {'type': 'numeric'}}], 'inherits': ['t-1']}})
sql = self.to_sql(inmap, ["CREATE SCHEMA \"s-d\""])
print(sql)
assert fix_indent(sql[0]) == "CREATE TABLE \"s-d\".\"t-1\" " \
"(\"c-1\" integer, c2 text)"
assert fix_indent(sql[1]) == "CREATE TABLE \"s-d\".\"t-2\" " \
"(c3 numeric) INHERITS (\"s-d\".\"t-1\")"
def test_drop_inherited(self):
"Drop tables that inherit from others"
stmts = [CREATE_STMT, "CREATE TABLE t2 (c3 numeric) INHERITS (t1)",
"CREATE TABLE t3 (c4 date) INHERITS (t2)"]
sql = self.to_sql(self.std_map(), stmts)
assert sql == ["DROP TABLE sd.t3", "DROP TABLE sd.t2",
"DROP TABLE sd.t1"]
+104
View File
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
"""Test tablespaces
These tests require the existence of tablespaces ts1 and ts2.
They should be owned by the user running the tests or the user should
have been granted CREATE (or ALL) privileges on the tablespaces.
"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TABLE = "CREATE TABLE sd.t1 (c1 integer, c2 text) TABLESPACE ts1"
class ToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created tables"""
def test_map_table(self):
"Map a table using a tablespace"
dbmap = self.to_map([CREATE_TABLE])
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'tablespace': 'ts1'}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_primary_key(self):
"Map a table with a PRIMARY KEY using a tablespace"
dbmap = self.to_map(["CREATE TABLE t1 (c1 integer PRIMARY KEY "
"USING INDEX TABLESPACE ts1, c2 text)"])
expmap = {'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'text'}}],
'primary_key': {'t1_pkey': {'columns': ['c1'],
'tablespace': 'ts1'}}}
assert dbmap['schema sd']['table t1'] == expmap
def test_map_index(self):
"Map an index using a tablespace"
dbmap = self.to_map(["CREATE TABLE t1 (c1 integer, c2 text)",
"CREATE UNIQUE INDEX t1_idx ON t1 (c1) "
"TABLESPACE ts1"])
expmap = {'t1_idx': {'keys': ['c1'], 'tablespace': 'ts1',
'unique': True}}
assert dbmap['schema sd']['table t1']['indexes'] == expmap
class ToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation of table statements from input schemas"""
def test_create_table(self):
"Create a table in a tablespace"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'tablespace': 'ts1'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE
def test_move_table(self):
"Move a table from one tablespace to another"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'tablespace': 'ts2'}})
sql = self.to_sql(inmap, [CREATE_TABLE])
assert fix_indent(sql[0]) == "ALTER TABLE sd.t1 SET TABLESPACE ts2"
def test_create_primary_key(self):
"Create a table with a PRIMARY KEY in a different tablespace"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer', 'not_null': True}},
{'c2': {'type': 'text'}}],
'primary_key': {'t1_pkey': {'columns': ['c1'],
'tablespace': 'ts2'}},
'tablespace': 'ts1'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TABLE sd.t1 " \
"(c1 integer NOT NULL, c2 text) TABLESPACE ts1"
assert fix_indent(sql[1]) == "ALTER TABLE sd.t1 ADD CONSTRAINT " \
"t1_pkey PRIMARY KEY (c1) USING INDEX TABLESPACE ts2"
def test_create_index(self):
"Create an index using a tablespace"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'], 'tablespace': 'ts2',
'unique': True}},
'tablespace': 'ts1'}})
sql = self.to_sql(inmap, [CREATE_TABLE])
assert fix_indent(sql[0]) == "CREATE UNIQUE INDEX t1_idx " \
"ON sd.t1 (c1) TABLESPACE ts2"
def test_move_index(self):
"Move a index from one tablespace to another"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}}],
'indexes': {'t1_idx': {'keys': ['c1'], 'tablespace': 'ts2'}}}})
stmts = ["CREATE TABLE t1 (c1 integer, c2 text)",
"CREATE INDEX t1_idx ON t1 (c1) TABLESPACE ts1"]
sql = self.to_sql(inmap, stmts)
assert fix_indent(sql[0]) == "ALTER INDEX sd.t1_idx SET TABLESPACE ts2"
+276
View File
@@ -0,0 +1,276 @@
# -*- coding: utf-8 -*-
"""Test text search objects"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_TSC_STMT = "CREATE TEXT SEARCH CONFIGURATION sd.tsc1 (PARSER = tsp1)"
CREATE_TSD_STMT = "CREATE TEXT SEARCH DICTIONARY sd.tsd1 (TEMPLATE = simple, "\
"stopwords = 'english')"
CREATE_TSP_STMT = "CREATE TEXT SEARCH PARSER sd.tsp1 (START = prsd_start, " \
"GETTOKEN = prsd_nexttoken, END = prsd_end, LEXTYPES = prsd_lextype, " \
"HEADLINE = prsd_headline)"
CREATE_TST_STMT = "CREATE TEXT SEARCH TEMPLATE sd.tst1 (INIT = dsimple_init, "\
"LEXIZE = dsimple_lexize)"
COMMENT_TSC_STMT = "COMMENT ON TEXT SEARCH CONFIGURATION sd.tsc1 IS " \
"'Test configuration tsc1'"
COMMENT_TSD_STMT = "COMMENT ON TEXT SEARCH DICTIONARY sd.tsd1 IS " \
"'Test dictionary tsd1'"
COMMENT_TSP_STMT = "COMMENT ON TEXT SEARCH PARSER sd.tsp1 IS " \
"'Test parser tsp1'"
COMMENT_TST_STMT = "COMMENT ON TEXT SEARCH TEMPLATE sd.tst1 IS " \
"'Test template tst1'"
class TextSearchConfigToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing text search configurations"""
superuser = True
def test_map_ts_config(self):
"Map an existing text search configuration"
stmts = [CREATE_TSP_STMT, CREATE_TSC_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search configuration tsc1'] == {
'parser': 'tsp1'}
def test_map_cross_schema_ts_config(self):
"Map a text search config with parser in different schema"
stmts = ["CREATE SCHEMA s1",
"CREATE TEXT SEARCH PARSER s1.tsp1 "
"(START = prsd_start, GETTOKEN = prsd_nexttoken, "
"END = prsd_end, LEXTYPES = prsd_lextype)",
"CREATE TEXT SEARCH CONFIGURATION tsc1 (PARSER = s1.tsp1)"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search configuration tsc1'] == {
'parser': 's1.tsp1'}
def test_map_ts_config_comment(self):
"Map a text search configuration with a comment"
stmts = [CREATE_TSP_STMT, CREATE_TSC_STMT, COMMENT_TSC_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search configuration tsc1'][
'description'] == 'Test configuration tsc1'
class TextSearchConfigToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input text search configurations"""
def test_create_ts_config(self):
"Create a text search configuration that didn't exist"
inmap = self.std_map()
inmap['schema sd'].update({'text search parser tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype',
'headline': 'prsd_headline'}, 'text search configuration tsc1': {
'parser': 'tsp1'}})
sql = self.to_sql(inmap, [CREATE_TSP_STMT])
assert fix_indent(sql[0]) == CREATE_TSC_STMT
def test_create_ts_config_in_schema(self):
"Create a text search config with parser in non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'text search parser tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype'}}})
inmap['schema sd'].update({'text search configuration tsc1': {
'parser': 's1.tsp1'}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == "CREATE TEXT SEARCH PARSER s1.tsp1 " \
"(START = prsd_start, GETTOKEN = prsd_nexttoken, " \
"END = prsd_end, LEXTYPES = prsd_lextype)"
assert fix_indent(sql[1]) == \
"CREATE TEXT SEARCH CONFIGURATION sd.tsc1 (PARSER = s1.tsp1)"
def test_bad_map_ts_config_(self):
"Error creating a text search configuration with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'tsc1': {'parser': 'tsp1'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_ts_config(self):
"Drop an existing text search configuration"
stmts = [CREATE_TSP_STMT, CREATE_TSC_STMT]
sql = self.to_sql(self.std_map(), stmts, superuser=True)
assert sql[0] == "DROP TEXT SEARCH CONFIGURATION sd.tsc1"
assert sql[1] == "DROP TEXT SEARCH PARSER sd.tsp1"
def test_comment_on_ts_config(self):
"Create a comment for an existing text search configuration"
stmts = [CREATE_TSP_STMT, CREATE_TSC_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'text search configuration tsc1': {
'parser': 'tsp1', 'description': "Test configuration tsc1"},
'text search parser tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype',
'headline': 'prsd_headline'}})
sql = self.to_sql(inmap, stmts, superuser=True)
assert sql == [COMMENT_TSC_STMT]
class TextSearchDictToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing text search dictionaries"""
def test_map_ts_dict(self):
"Map an existing text search dictionary"
dbmap = self.to_map([CREATE_TSD_STMT])
assert dbmap['schema sd']['text search dictionary tsd1'] == {
'template': 'simple', 'options': "stopwords = 'english'"}
def test_map_ts_dict_comment(self):
"Map a text search dictionary with a comment"
stmts = [CREATE_TSD_STMT, COMMENT_TSD_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search dictionary tsd1'][
'description'], 'Test dictionary tsd1'
class TextSearchDictToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input text search dictionaries"""
def test_create_ts_dict(self):
"Create a text search dictionary that didn't exist"
inmap = self.std_map()
inmap['schema sd'].update({'text search dictionary tsd1': {
'template': 'simple', 'options': "stopwords = 'english'"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TSD_STMT
def test_bad_map_ts_dict(self):
"Error creating a text search dictionary with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'tsd1': {
'template': 'simple', 'options': "stopwords = 'english'"}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_ts_dict(self):
"Drop an existing text search dictionary"
sql = self.to_sql(self.std_map(), [CREATE_TSD_STMT])
assert sql == ["DROP TEXT SEARCH DICTIONARY sd.tsd1"]
def test_comment_on_ts_dict(self):
"Create a comment for an existing text search dictionary"
inmap = self.std_map()
inmap['schema sd'].update({'text search dictionary tsd1': {
'template': 'simple', 'options': "stopwords = 'english'",
'description': "Test dictionary tsd1"}})
sql = self.to_sql(inmap, [CREATE_TSD_STMT])
assert sql == [COMMENT_TSD_STMT]
class TextSearchParserToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing text search parsers"""
superuser = True
def test_map_ts_parser(self):
"Map an existing text search parser"
stmts = [CREATE_TSP_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search parser tsp1'] == {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype',
'headline': 'prsd_headline'}
def test_map_ts_parser_comment(self):
"Map a text search parser with a comment"
stmts = [CREATE_TSP_STMT, COMMENT_TSP_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search parser tsp1'][
'description'] == 'Test parser tsp1'
class TextSearchParserToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input text search parsers"""
def test_create_ts_parser(self):
"Create a text search parser that didn't exist"
inmap = self.std_map()
inmap['schema sd'].update({'text search parser tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype',
'headline': 'prsd_headline'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TSP_STMT
def test_bad_map_ts_parser(self):
"Error creating a text search parser with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_ts_parser(self):
"Drop an existing text search parser"
sql = self.to_sql(self.std_map(), [CREATE_TSP_STMT], superuser=True)
assert sql == ["DROP TEXT SEARCH PARSER sd.tsp1"]
def test_comment_on_ts_parser(self):
"Create a comment for an existing text search parser"
inmap = self.std_map()
inmap['schema sd'].update({'text search parser tsp1': {
'start': 'prsd_start', 'gettoken': 'prsd_nexttoken',
'end': 'prsd_end', 'lextypes': 'prsd_lextype',
'headline': 'prsd_headline', 'description': "Test parser tsp1"}})
sql = self.to_sql(inmap, [CREATE_TSP_STMT], superuser=True)
assert sql == [COMMENT_TSP_STMT]
class TextSearchTemplateToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing text search templates"""
superuser = True
def test_map_ts_template(self):
"Map an existing text search template"
dbmap = self.to_map([CREATE_TST_STMT])
assert dbmap['schema sd']['text search template tst1'] == {
'init': 'dsimple_init', 'lexize': 'dsimple_lexize'}
def test_map_ts_template_comment(self):
"Map a text search template with a comment"
stmts = [CREATE_TST_STMT, COMMENT_TST_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['text search template tst1'][
'description'], 'Test template tst1'
class TextSearchTemplateToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation for input text search templates"""
def test_create_ts_template(self):
"Create a text search template that didn't exist"
inmap = self.std_map()
inmap['schema sd'].update({'text search template tst1': {
'init': 'dsimple_init', 'lexize': 'dsimple_lexize'}})
sql = self.to_sql(inmap, superuser=True)
assert fix_indent(sql[0]) == CREATE_TST_STMT
def test_bad_map_ts_template(self):
"Error creating a text search template with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'tst1': {
'init': 'dsimple_init', 'lexize': 'dsimple_lexize'}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_ts_template(self):
"Drop an existing text search template"
sql = self.to_sql(self.std_map(), [CREATE_TST_STMT], superuser=True)
assert sql == ["DROP TEXT SEARCH TEMPLATE sd.tst1"]
def test_comment_on_ts_template(self):
"Create a comment for an existing text search template"
inmap = self.std_map()
inmap['schema sd'].update({'text search template tst1': {
'init': 'dsimple_init', 'lexize': 'dsimple_lexize',
'description': "Test template tst1"}})
sql = self.to_sql(inmap, [CREATE_TST_STMT], superuser=True)
assert sql == [COMMENT_TST_STMT]
+488
View File
@@ -0,0 +1,488 @@
# -*- coding: utf-8 -*-
"""Test triggers"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
FUNC_SRC = "BEGIN NEW.c3 := CURRENT_DATE; RETURN NEW; END"
FUNC_INSTEAD_SRC = "BEGIN INSERT INTO t1 VALUES (NEW.c1, NEW.c2, now()); " \
"RETURN NULL; END"
FUNC_REF_NEW_SRC = "BEGIN SELECT SUM(c2) FROM ins; RETURN NULL; END"
FUNC_REF_NEW_OLD_SRC = "BEGIN SELECT SUM(c2) FROM del; " \
"SELECT SUM(c2) FROM ins; RETURN NULL; END"
CREATE_TABLE_STMT = "CREATE TABLE sd.t1 (c1 integer, c2 text, " \
"c3 date)"
CREATE_TABLE_STMT2 = "CREATE TABLE t1 (c1 integer, c2 text, " \
"c3 text, tsidx tsvector)"
CREATE_TABLE_STMT3 = "CREATE TABLE sd.t1 (c1 integer, c2 money)"
CREATE_FUNC_STMT = "CREATE FUNCTION sd.f1() RETURNS trigger LANGUAGE plpgsql" \
" AS $_$%s$_$" % FUNC_SRC
CREATE_STMT = "CREATE TRIGGER tr1 BEFORE INSERT OR UPDATE ON sd.t1 " \
"FOR EACH ROW EXECUTE PROCEDURE sd.f1()"
COMMENT_STMT = "COMMENT ON TRIGGER tr1 ON sd.t1 IS 'Test trigger tr1'"
class TriggerToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing triggers"""
def test_map_trigger1(self):
"Map a simple trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 'sd.f1'}}
def test_map_trigger2(self):
"Map another simple trigger with different attributes"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT,
"CREATE TRIGGER tr1 AFTER DELETE OR TRUNCATE ON t1 "
"EXECUTE PROCEDURE f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'after', 'events': ['delete', 'truncate'],
'level': 'statement', 'procedure': 'sd.f1'}}
def test_map_trigger_update_cols(self):
"Map trigger with UPDATE OF columns"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT,
"CREATE TRIGGER tr1 AFTER INSERT OR UPDATE OF c1, c2 ON t1 "
"FOR EACH ROW EXECUTE PROCEDURE f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'after', 'events': ['insert', 'update'],
'columns': ['c1', 'c2'], 'level': 'row',
'procedure': 'sd.f1'}}
def test_map_trigger_conditional(self):
"Map trigger with a WHEN qualification"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT,
"CREATE TRIGGER tr1 AFTER UPDATE ON t1 FOR EACH ROW "
"WHEN (OLD.c2 IS DISTINCT FROM NEW.c2) "
"EXECUTE PROCEDURE f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'after', 'events': ['update'],
'level': 'row', 'procedure': 'sd.f1',
'condition': '(old.c2 IS DISTINCT FROM new.c2)'}}
def test_map_trigger_instead(self):
"Map an INSTEAD OF trigger"
stmts = [CREATE_TABLE_STMT, "CREATE VIEW v1 AS SELECT c1, c2 FROM t1",
"CREATE FUNCTION f1() RETURNS trigger LANGUAGE plpgsql AS "
"$_$%s$_$" % FUNC_INSTEAD_SRC,
"CREATE TRIGGER tr1 INSTEAD OF INSERT ON v1 "
"FOR EACH ROW EXECUTE PROCEDURE f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['view v1']['triggers'] == {
'tr1': {'timing': 'instead of', 'events': ['insert'],
'level': 'row', 'procedure': 'sd.f1'}}
def test_map_tsvector_trigger(self):
"Map a text search (tsvector) trigger"
stmts = [
CREATE_TABLE_STMT2,
"CREATE TRIGGER tr1 BEFORE INSERT OR UPDATE ON sd.t1 "
"FOR EACH ROW EXECUTE PROCEDURE "
"tsvector_update_trigger('tsidx', 'pg_catalog.english', 'c2')"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'before', 'events': ['insert', 'update'],
'level': 'row',
'procedure': {'name': 'tsvector_update_trigger',
'arguments':
"'tsidx', 'pg_catalog.english', 'c2'"}}}
def test_map_trigger_function_distinct_schemas(self):
"Map a trigger in a non-default schema with function in different one"
stmts = ["CREATE SCHEMA s1", "CREATE TABLE s1.t1 (c1 integer, "
"c2 text, c3 date)", "CREATE SCHEMA s2",
"CREATE FUNCTION s2.f1() RETURNS trigger LANGUAGE plpgsql AS "
"$_$%s$_$" % FUNC_SRC,
"CREATE TRIGGER tr1 BEFORE INSERT OR UPDATE ON s1.t1 "
"FOR EACH ROW EXECUTE PROCEDURE s2.f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema s1']['table t1']['triggers'] == {
'tr1': {'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 's2.f1'}}
def test_map_trigger_referencing_new(self):
"Map a trigger that uses REFERENCING NEW TABLE"
stmts = [CREATE_TABLE_STMT3,
"CREATE FUNCTION f1() RETURNS trigger LANGUAGE plpgsql AS "
"$_$%s$_$" % FUNC_REF_NEW_SRC,
"CREATE TRIGGER tr1 AFTER INSERT ON t1 "
"REFERENCING NEW TABLE AS ins "
"FOR EACH STATEMENT EXECUTE FUNCTION f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'after', 'events': ['insert'],
'level': 'statement', 'procedure': 'sd.f1',
'referencing_new': 'ins'}}
def test_map_trigger_referencing_new_old(self):
"Map a trigger that uses REFERENCING NEW and OLD TABLE"
stmts = [CREATE_TABLE_STMT3,
"CREATE FUNCTION f1() RETURNS trigger LANGUAGE plpgsql AS "
"$_$%s$_$" % FUNC_REF_NEW_OLD_SRC,
"CREATE TRIGGER tr1 AFTER UPDATE ON t1 "
"REFERENCING NEW TABLE AS ins OLD TABLE AS del "
"FOR EACH ROW EXECUTE FUNCTION f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'timing': 'after', 'events': ['update'],
'level': 'row', 'procedure': 'sd.f1',
'referencing_new': 'ins', 'referencing_old': 'del'}}
def test_map_trigger_comment(self):
"Map a trigger comment"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT,
COMMENT_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers']['tr1'][
'description'] == 'Test trigger tr1'
class ConstraintTriggerToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of existing constraint triggers"""
def test_map_trigger(self):
"Map a simple constraint trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT,
"CREATE CONSTRAINT TRIGGER tr1 AFTER INSERT OR UPDATE ON t1 "
"FOR EACH ROW EXECUTE PROCEDURE f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'constraint': True, 'timing': 'after',
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.f1'}}
def test_map_trigger_deferrable(self):
"Map a deferrable, initially deferred constraint trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT,
"CREATE CONSTRAINT TRIGGER tr1 AFTER INSERT OR UPDATE ON t1 "
"DEFERRABLE INITIALLY DEFERRED "
"FOR EACH ROW EXECUTE PROCEDURE f1()"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['table t1']['triggers'] == {
'tr1': {'constraint': True, 'deferrable': True,
'initially_deferred': True, 'timing': 'after',
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.f1'}}
class TriggerToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input triggers"""
def test_create_trigger1(self):
"Create a simple trigger"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == CREATE_STMT
def test_create_trigger2(self):
"Create another simple trigger with"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {'timing': 'after',
'events': ['delete', 'truncate'],
'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == "CREATE TRIGGER tr1 AFTER DELETE OR " \
"TRUNCATE ON sd.t1 FOR EACH STATEMENT EXECUTE PROCEDURE sd.f1()"
def test_create_trigger_update_cols(self):
"Create a trigger with UPDATE OF columns"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {'timing': 'before', 'events': [
'insert', 'update'], 'columns': ['c1', 'c2'], 'level': 'row',
'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == "CREATE TRIGGER tr1 BEFORE INSERT OR " \
"UPDATE OF c1, c2 ON sd.t1 FOR EACH ROW EXECUTE PROCEDURE sd.f1()"
def test_create_trigger_conditional(self):
"Create a trigger with a WHEN qualification"
inmap = self.std_map()
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {'timing': 'before', 'events': [
'update'], 'level': 'row', 'procedure': 'sd.f1',
'condition': '(old.c2 IS DISTINCT FROM new.c2)'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == "CREATE TRIGGER tr1 BEFORE UPDATE " \
"ON sd.t1 FOR EACH ROW WHEN ((old.c2 IS DISTINCT FROM new.c2)) " \
"EXECUTE PROCEDURE sd.f1()"
def test_create_trigger_instead(self):
"Create an INSTEAD OF trigger"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger',
'source': FUNC_INSTEAD_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}]},
'view v1': {'definition': "SELECT c1, c2 FROM t1",
'triggers': {'tr1': {'timing': 'instead of',
'events': ['insert'],
'level': 'row',
'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE_STMT
cr1, cr2 = (1, 2) if 'VIEW' in sql[1] else (2, 1)
assert fix_indent(sql[cr1]) == \
"CREATE VIEW sd.v1 AS SELECT c1, c2 FROM t1"
assert fix_indent(sql[cr2]) == "CREATE FUNCTION sd.f1() RETURNS " \
"trigger LANGUAGE plpgsql AS $_$%s$_$" % FUNC_INSTEAD_SRC
assert fix_indent(sql[3]) == "CREATE TRIGGER tr1 INSTEAD OF INSERT " \
"ON sd.v1 FOR EACH ROW EXECUTE PROCEDURE sd.f1()"
def test_add_tsvector_trigger(self):
"Add a text search (tsvector) trigger"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'text'}},
{'tsidx': {'type': 'tsvector'}}],
'triggers': {'t1_tsidx_update': {
'timing': 'before',
'events': ['insert', 'update'], 'level': 'row',
'procedure': {'name': 'tsvector_update_trigger',
'arguments':
"'tsidx', 'pg_catalog.english', 'c2'"}}}}})
sql = self.to_sql(inmap, [CREATE_TABLE_STMT2])
assert fix_indent(sql[0]) == "CREATE TRIGGER t1_tsidx_update BEFORE" \
" INSERT OR UPDATE ON sd.t1 FOR EACH ROW EXECUTE PROCEDURE " \
"tsvector_update_trigger('tsidx', 'pg_catalog.english', 'c2')"
def test_change_tsvector_trigger(self):
"Change a text search (tsvector) trigger"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'text'}},
{'tsidx': {'type': 'tsvector'}}],
'triggers': {'t1_tsidx_update': {
'timing': 'before', 'events': ['insert', 'update'],
'level': 'row',
'procedure': {'name': "tsvector_update_trigger",
'arguments':
"'tsidx', 'pg_catalog.english', 'c2', 'c3'"}}}}})
stmts = [CREATE_TABLE_STMT2,
"CREATE TRIGGER t1_tsidx_update BEFORE INSERT OR UPDATE ON "
"t1 FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger"
"('tsidx', 'pg_catalog.english', 'c2')"]
sql = self.to_sql(inmap, stmts)
assert sql[0] == "DROP TRIGGER t1_tsidx_update ON sd.t1"
assert fix_indent(sql[1]) == "CREATE TRIGGER t1_tsidx_update BEFORE" \
" INSERT OR UPDATE ON sd.t1 FOR EACH ROW EXECUTE PROCEDURE " \
"tsvector_update_trigger('tsidx', 'pg_catalog.english', " \
"'c2', 'c3')"
def test_create_trigger_function_distinct_schemas(self):
"Create a trigger in non-default schema with function in different one"
inmap = self.std_map(plpgsql_installed=True)
inmap.update({'schema s2': {'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}},
'schema s1': {
'table t1': {
'columns': [
{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'timing': 'before',
'events': ['insert', 'update'],
'level': 'row', 'procedure': 's2.f1'}}}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1", "CREATE SCHEMA s2"])
assert fix_indent(sql[2]) == "CREATE TRIGGER tr1 BEFORE INSERT OR " \
"UPDATE ON s1.t1 FOR EACH ROW EXECUTE PROCEDURE s2.f1()"
def test_create_trigger_referencing(self):
"Create a trigger that uses REFERENCING OLD and NEW"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger',
'source': FUNC_REF_NEW_OLD_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'money'}}],
'triggers': {'tr1': {'timing': 'after', 'events': ['update'],
'level': 'row', 'referencing_new': 'ins',
'referencing_old': 'del',
'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TABLE_STMT3
assert fix_indent(sql[1]) == "CREATE FUNCTION sd.f1() RETURNS " \
"trigger LANGUAGE plpgsql AS $_$%s$_$" % FUNC_REF_NEW_OLD_SRC
assert fix_indent(sql[2]) == "CREATE TRIGGER tr1 AFTER UPDATE " \
"ON sd.t1 REFERENCING NEW TABLE AS ins OLD TABLE AS del " \
"FOR EACH ROW EXECUTE PROCEDURE sd.f1()"
def test_drop_trigger(self):
"Drop an existing trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT]
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}]}})
sql = self.to_sql(inmap, stmts)
assert sql == ["DROP TRIGGER tr1 ON sd.t1"]
def test_drop_trigger_table(self):
"Drop an existing trigger and the related table"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT]
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
sql = self.to_sql(inmap, stmts)
assert sql[0] == "DROP TRIGGER tr1 ON sd.t1"
assert sql[1] == "DROP TABLE sd.t1"
def test_trigger_with_comment(self):
"Create a trigger with a comment"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'description': 'Test trigger tr1',
'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == CREATE_STMT
assert sql[3] == COMMENT_STMT
def test_comment_on_trigger(self):
"Create a comment on an existing trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT]
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'description': 'Test trigger tr1',
'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == [COMMENT_STMT]
def test_drop_trigger_comment(self):
"Drop a comment on an existing trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT,
COMMENT_STMT]
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON TRIGGER tr1 ON sd.t1 IS NULL"]
def test_change_trigger_comment(self):
"Change existing comment on a trigger"
stmts = [CREATE_TABLE_STMT, CREATE_FUNC_STMT, CREATE_STMT,
COMMENT_STMT]
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'description': 'Changed trigger tr1',
'timing': 'before', 'events': ['insert', 'update'],
'level': 'row', 'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap, stmts)
assert sql == [
"COMMENT ON TRIGGER tr1 ON sd.t1 IS 'Changed trigger tr1'"]
class ConstraintTriggerToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input triggers"""
def test_create_trigger(self):
"Create a constraint trigger"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'constraint': True, 'timing': 'after',
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == "CREATE CONSTRAINT TRIGGER tr1 AFTER " \
"INSERT OR UPDATE ON sd.t1 FOR EACH ROW EXECUTE PROCEDURE sd.f1()"
def test_create_trigger_deferrable(self):
"Create a deferrable constraint trigger"
inmap = self.std_map(plpgsql_installed=True)
inmap['schema sd'].update({'function f1()': {
'language': 'plpgsql', 'returns': 'trigger', 'source': FUNC_SRC}})
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'date'}}],
'triggers': {'tr1': {
'constraint': True, 'deferrable': True,
'initially_deferred': True, 'timing': 'after',
'events': ['insert', 'update'], 'level': 'row',
'procedure': 'sd.f1'}}}})
sql = self.to_sql(inmap)
crt0, crt1 = (0, 1) if 'TABLE' in sql[0] else (1, 0)
assert fix_indent(sql[crt0]) == CREATE_TABLE_STMT
assert fix_indent(sql[crt1]) == CREATE_FUNC_STMT
assert fix_indent(sql[2]) == "CREATE CONSTRAINT TRIGGER tr1 " \
"AFTER INSERT OR UPDATE ON sd.t1 DEFERRABLE INITIALLY " \
"DEFERRED FOR EACH ROW EXECUTE PROCEDURE sd.f1()"
+250
View File
@@ -0,0 +1,250 @@
# -*- coding: utf-8 -*-
"""Test enums and other types"""
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_COMPOSITE_STMT = "CREATE TYPE sd.t1 AS " \
"(x integer, y integer, z integer)"
CREATE_ENUM_STMT = "CREATE TYPE sd.t1 AS ENUM ('red', 'green', 'blue')"
CREATE_SHELL_STMT = "CREATE TYPE sd.t1"
CREATE_RANGE_STMT = "CREATE TYPE sd.t1 AS RANGE (SUBTYPE = smallint)"
CREATE_FUNC_IN = "CREATE FUNCTION sd.t1textin(cstring) RETURNS t1 " \
"LANGUAGE internal IMMUTABLE STRICT AS $$textin$$"
CREATE_FUNC_OUT = "CREATE FUNCTION sd.t1textout(sd.t1) RETURNS cstring " \
"LANGUAGE internal IMMUTABLE STRICT AS $$textout$$"
CREATE_TYPE_STMT = "CREATE TYPE t1 (INPUT = t1textin, OUTPUT = t1textout)"
COMMENT_STMT = "COMMENT ON TYPE t1 IS 'Test type t1'"
class CompositeToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created composite types"""
def test_composite(self):
"Map a composite type"
dbmap = self.to_map([CREATE_COMPOSITE_STMT])
assert dbmap['schema sd']['type t1'] == {
'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}
def test_dropped_attribute(self):
"Map a composite type which has a dropped attribute"
stmts = [CREATE_COMPOSITE_STMT, "ALTER TYPE t1 DROP ATTRIBUTE y"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['type t1'] == {
'attributes': [{'x': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}
class CompositeToSqlTestCase(InputMapToSqlTestCase):
"""Test creation and modification of composite types"""
def test_create_composite(self):
"Create a composite type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_COMPOSITE_STMT
def test_drop_composite(self):
"Drop an existing composite"
sql = self.to_sql(self.std_map(), [CREATE_COMPOSITE_STMT])
assert sql == ["DROP TYPE sd.t1"]
def test_rename_composite(self):
"Rename an existing composite"
inmap = self.std_map()
inmap['schema sd'].update({'type t2': {
'oldname': 't1',
'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}})
sql = self.to_sql(inmap, [CREATE_COMPOSITE_STMT])
assert sql == ["ALTER TYPE sd.t1 RENAME TO t2"]
def test_add_attribute(self):
"Add an attribute to a composite type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'y': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}})
sql = self.to_sql(inmap, ["CREATE TYPE t1 AS (x integer, y integer)"])
assert fix_indent(sql[0]) == "ALTER TYPE sd.t1 ADD ATTRIBUTE z integer"
def test_drop_attribute(self):
"Drop an attribute from a composite type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}})
sql = self.to_sql(inmap, [CREATE_COMPOSITE_STMT])
assert fix_indent(sql[0]) == "ALTER TYPE sd.t1 DROP ATTRIBUTE y"
def test_drop_attribute_schema(self):
"Drop an attribute from a composite type within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'z': {'type': 'integer'}}]}}})
sql = self.to_sql(inmap, [
"CREATE SCHEMA s1",
"CREATE TYPE s1.t1 AS (x integer, y integer, z integer)"])
assert fix_indent(sql[0]) == "ALTER TYPE s1.t1 DROP ATTRIBUTE y"
def test_rename_attribute(self):
"Rename an attribute of a composite type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'attributes': [{'x': {'type': 'integer'}},
{'y1': {'type': 'integer', 'oldname': 'y'}},
{'z': {'type': 'integer'}}]}})
sql = self.to_sql(inmap, [CREATE_COMPOSITE_STMT])
assert fix_indent(sql[0]) == \
"ALTER TYPE sd.t1 RENAME ATTRIBUTE y TO y1"
class EnumToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created enum types"""
def test_enum(self):
"Map an enum"
dbmap = self.to_map([CREATE_ENUM_STMT])
assert dbmap['schema sd']['type t1'] == {
'labels': ['red', 'green', 'blue']}
class EnumToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input enums"""
def test_create_enum(self):
"Create an enum"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'labels': ['red', 'green', 'blue']}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_ENUM_STMT
def test_drop_enum(self):
"Drop an existing enum"
sql = self.to_sql(self.std_map(), [CREATE_ENUM_STMT])
assert sql == ["DROP TYPE sd.t1"]
def test_change_enum(self):
"Change an existing enum"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'labels': ['red', 'yellow', 'blue']}})
sql = self.to_sql(inmap, [CREATE_ENUM_STMT])
assert sql[0] == "DROP TYPE sd.t1"
assert fix_indent(sql[1]) == \
"CREATE TYPE sd.t1 AS ENUM ('red', 'yellow', 'blue')"
def test_rename_enum(self):
"Rename an existing enum"
inmap = self.std_map()
inmap['schema sd'].update({'type t2': {
'oldname': 't1', 'labels': ['red', 'green', 'blue']}})
sql = self.to_sql(inmap, [CREATE_ENUM_STMT])
assert sql == ["ALTER TYPE sd.t1 RENAME TO t2"]
class BaseTypeToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created base type types"""
superuser = True
def test_base_type(self):
"Map a base type"
stmts = [CREATE_SHELL_STMT, CREATE_FUNC_IN, CREATE_FUNC_OUT,
CREATE_TYPE_STMT]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['type t1'] == {
'input': 't1textin', 'output': 't1textout',
'internallength': 'variable', 'alignment': 'int4',
'storage': 'plain', 'category': 'U'}
def test_base_type_category(self):
"Map a base type"
stmts = [CREATE_SHELL_STMT, CREATE_FUNC_IN, CREATE_FUNC_OUT,
"CREATE TYPE t1 (INPUT = t1textin, OUTPUT = t1textout, "
"CATEGORY = 'S')"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['type t1'] == {
'input': 't1textin', 'output': 't1textout',
'internallength': 'variable', 'alignment': 'int4',
'storage': 'plain', 'category': 'S'}
class BaseTypeToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input base types"""
def test_create_base_type(self):
"Create a base type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'input': 't1textin', 'output': 't1textout',
'internallength': 'variable', 'alignment': 'int4',
'storage': 'plain'}, 'function t1textin(cstring)': {
'language': 'internal', 'returns': 't1', 'strict': True,
'volatility': 'immutable', 'source': 'textin'},
'function t1textout(sd.t1)': {
'language': 'internal', 'returns': 'cstring',
'strict': True, 'volatility': 'immutable',
'source': 'textout'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_SHELL_STMT
assert fix_indent(sql[1]) == CREATE_FUNC_IN
assert fix_indent(sql[2]) == CREATE_FUNC_OUT
assert fix_indent(sql[3]) == "CREATE TYPE sd.t1 (INPUT = t1textin, " \
"OUTPUT = t1textout, INTERNALLENGTH = variable, " \
"ALIGNMENT = int4, STORAGE = plain)"
def test_drop_type(self):
"Drop an existing base type"
stmts = [CREATE_SHELL_STMT, CREATE_FUNC_IN, CREATE_FUNC_OUT,
CREATE_TYPE_STMT]
sql = self.to_sql(self.std_map(), stmts, superuser=True)
assert sql == ["DROP TYPE sd.t1 CASCADE"]
class RangeToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created RANGE types"""
def test_range_simple(self):
"Map a simple range type"
dbmap = self.to_map([CREATE_RANGE_STMT])
assert dbmap['schema sd']['type t1'] == {'subtype': 'int2'}
def test_range_subtypediff(self):
"Map a range type with a subtype difference function"
stmts = ["CREATE TYPE t1 AS RANGE (SUBTYPE = float8, "
"SUBTYPE_DIFF = float8mi)"]
dbmap = self.to_map(stmts)
assert dbmap['schema sd']['type t1'] == {
'subtype': 'float8', 'subtype_diff': 'float8mi'}
class RangeToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input range types"""
def test_create_range_simple(self):
"Create a range type"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {'subtype': 'smallint'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_RANGE_STMT
def test_create_range_subtype_diff(self):
"Create a range with a subtype diff function"
inmap = self.std_map()
inmap['schema sd'].update({'type t1': {
'subtype': 'float8', 'subtype_diff': 'float8mi'}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == (
"CREATE TYPE sd.t1 AS RANGE (SUBTYPE = float8, "
"SUBTYPE_DIFF = float8mi)")
+217
View File
@@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
"""Test views"""
import pytest
from pyrseas.testutils import DatabaseToMapTestCase
from pyrseas.testutils import InputMapToSqlTestCase, fix_indent
CREATE_STMT = "CREATE VIEW sd.v1 AS SELECT now()::date AS today"
CREATE_TBL = "CREATE TABLE sd.t1 (c1 integer, c2 text, c3 integer)"
CREATE_STMT2 = "CREATE VIEW sd.v1 AS SELECT c1, c3 * 2 AS c2 FROM t1"
COMMENT_STMT = "COMMENT ON VIEW sd.v1 IS 'Test view v1'"
VIEW_DEFN = " SELECT now()::date AS today;"
class ViewToMapTestCase(DatabaseToMapTestCase):
"""Test mapping of created views"""
def test_map_view_no_table(self):
"Map a created view without a table dependency"
dbmap = self.to_map([CREATE_STMT])
expmap = {'columns': [{'today': {'type': 'date'}}],
'definition': VIEW_DEFN}
assert dbmap['schema sd']['view v1'] == expmap
def test_map_view_table(self):
"Map a created view with a table dependency"
stmts = [CREATE_TBL, CREATE_STMT2]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'integer'}}],
'depends_on': ['table t1'],
'definition': " SELECT t1.c1,"
"\n t1.c3 * 2 AS c2\n FROM sd.t1;"}
assert dbmap['schema sd']['view v1'] == expmap
def test_map_view_columns(self):
"Map a complex view's columns in addition to its definition"
stmts = ["CREATE TABLE t1 (c1 INTEGER UNIQUE)",
"CREATE TABLE t2 (c2 INTEGER PRIMARY KEY REFERENCES t1(c1))",
"CREATE VIEW v1 AS SELECT (ROW(t1.*)::t2).*, t1, 5 AS const "
"FROM t1"]
dbmap = self.to_map(stmts)
expmap = {'columns': [{'c2': {'type': 'integer'}},
{'t1': {'type': 'sd.t1'}},
{'const': {'type': 'integer'}}],
'definition': " SELECT (ROW(t1.c1)::sd.t2).c2 AS c2,"
"\n t1.*::sd.t1 AS t1,\n 5 AS const\n FROM sd.t1;",
'depends_on': ['table t1']}
assert dbmap['schema sd']['view v1'] == expmap
def test_map_view_comment(self):
"Map a view with a comment"
dbmap = self.to_map([CREATE_STMT, COMMENT_STMT])
assert dbmap['schema sd']['view v1']['description'] == \
'Test view v1'
class ViewToSqlTestCase(InputMapToSqlTestCase):
"""Test SQL generation from input views"""
def test_create_view_no_table(self):
"Create a view with no table dependency"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {'definition': VIEW_DEFN}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
def test_create_view(self):
"Create a view"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}}, {'c2': {'type': 'text'}},
{'c3': {'type': 'integer'}}]}})
inmap['schema sd'].update({'view v1': {
'definition': "SELECT c1, c3 * 2 AS c2 FROM t1"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_TBL
assert fix_indent(sql[1]) == CREATE_STMT2
def test_create_view_in_schema(self):
"Create a view within a non-default schema"
inmap = self.std_map()
inmap.update({'schema s1': {'view v1': {'definition': VIEW_DEFN}}})
sql = self.to_sql(inmap, ["CREATE SCHEMA s1"])
assert fix_indent(sql[0]) == \
"CREATE VIEW s1.v1 AS SELECT now()::date AS today"
def test_bad_view_map(self):
"Error creating a view with a bad map"
inmap = self.std_map()
inmap['schema sd'].update({'v1': {'definition': VIEW_DEFN}})
with pytest.raises(KeyError):
self.to_sql(inmap)
def test_drop_view_no_table(self):
"Drop an existing view without a table dependency"
sql = self.to_sql(self.std_map(), [CREATE_STMT])
assert sql == ["DROP VIEW sd.v1"]
def test_drop_view(self):
"Drop an existing view with table dependencies"
stmts = ["CREATE TABLE t1 (c1 INTEGER, c2 TEXT)",
"CREATE TABLE t2 (c1 INTEGER, c3 TEXT)",
"CREATE VIEW v1 AS SELECT t1.c1, c2, c3 "
"FROM t1 JOIN t2 ON (t1.c1 = t2.c1)"]
sql = self.to_sql(self.std_map(), stmts)
assert sql[0] == "DROP VIEW sd.v1"
# can't control which table will be dropped first
drt1 = 1
drt2 = 2
if 't1' in sql[2]:
drt1 = 2
drt2 = 1
assert sql[drt1] == "DROP TABLE sd.t1"
assert sql[drt2] == "DROP TABLE sd.t2"
def test_rename_view(self):
"Rename an existing view"
inmap = self.std_map()
inmap['schema sd'].update({'view v2': {
'oldname': 'v1', 'definition': VIEW_DEFN}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == ["ALTER VIEW sd.v1 RENAME TO v2"]
def test_bad_rename_view(self):
"Error renaming a non-existing view"
inmap = self.std_map()
inmap['schema sd'].update({'view v2': {
'oldname': 'v3', 'definition': VIEW_DEFN}})
with pytest.raises(KeyError):
self.to_sql(inmap, [CREATE_STMT])
def test_change_view_defn(self):
"Change view definition"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'today': {'type': 'date'}}],
'definition': " SELECT 'now'::text::date AS today;"}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert fix_indent(sql[0]) == "CREATE OR REPLACE VIEW sd.v1 AS " \
"SELECT 'now'::text::date AS today"
def test_change_column_name(self):
"Attempt rename view column name (disallowed)"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'todays_date': {'type': 'date'}}],
'definition': " SELECT now()::date AS todays_date;"}})
with pytest.raises(KeyError):
sql = self.to_sql(inmap, [CREATE_STMT])
def test_change_column_type(self):
"Change view column type to different type (disallowed)"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'numeric'}}],
'definition': " SELECT c1, c3 * 2.0 AS c2 FROM t1;"}})
with pytest.raises(TypeError):
sql = self.to_sql(inmap, [CREATE_TBL, CREATE_STMT2])
def test_view_depend_pk(self):
"Create a view that depends on a primary key. See issue #72"
inmap = self.std_map()
inmap['schema sd'].update({'table t1': {
'columns': [{'c1': {'type': 'integer'}},
{'c2': {'type': 'text'}}],
'primary_key': {'t1_pkey': {'columns': ['c1']}}},
'view v1': {
'definition': " SELECT t1.c1,\n t1.c2\n FROM t1\n "
"GROUP BY t1.c1;",
'depends_on': ['table t1']}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == "CREATE TABLE sd.t1 (c1 integer, c2 text)"
assert fix_indent(sql[1]) == "ALTER TABLE sd.t1 ADD CONSTRAINT " \
"t1_pkey PRIMARY KEY (c1)"
assert fix_indent(sql[2]) == "CREATE VIEW sd.v1 AS SELECT t1.c1, " \
"t1.c2 FROM t1 GROUP BY t1.c1"
def test_view_with_comment(self):
"Create a view with a comment"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'definition': VIEW_DEFN, 'description': "Test view v1"}})
sql = self.to_sql(inmap)
assert fix_indent(sql[0]) == CREATE_STMT
assert sql[1] == COMMENT_STMT
def test_comment_on_view(self):
"Create a comment for an existing view"
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'today': {'type': 'date'}}],
'definition': VIEW_DEFN, 'description': "Test view v1"}})
sql = self.to_sql(inmap, [CREATE_STMT])
assert sql == [COMMENT_STMT]
def test_drop_view_comment(self):
"Drop the comment on an existing view"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'today': {'type': 'date'}}],
'definition': VIEW_DEFN}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON VIEW sd.v1 IS NULL"]
def test_change_view_comment(self):
"Change existing comment on a view"
stmts = [CREATE_STMT, COMMENT_STMT]
inmap = self.std_map()
inmap['schema sd'].update({'view v1': {
'columns': [{'today': {'type': 'date'}}],
'definition': VIEW_DEFN, 'description': "Changed view v1"}})
sql = self.to_sql(inmap, stmts)
assert sql == ["COMMENT ON VIEW sd.v1 IS 'Changed view v1'"]
+1
View File
@@ -0,0 +1 @@
"""Pyrseas functional tests"""
+125
View File
@@ -0,0 +1,125 @@
--
-- $Id: regressdatabase.sql,v 1.2 2006/02/13 01:15:56 rbt Exp $
--
BEGIN;
--
-- Foreign key'd structure, check constraints, primary keys
-- and duplicate table names in different schemas
--
CREATE SCHEMA product
CREATE TABLE product
( product_id SERIAL PRIMARY KEY
, product_code text NOT NULL UNIQUE
CHECK(product_code = upper(product_code))
, product_description text
);
CREATE SCHEMA store
CREATE TABLE store
( store_id SERIAL PRIMARY KEY
, store_code text NOT NULL UNIQUE
CHECK(store_code = upper(store_code))
, store_description text
)
CREATE TABLE inventory
( store_id integer REFERENCES store
ON UPDATE CASCADE ON DELETE RESTRICT
, product_id integer REFERENCES product.product
ON UPDATE CASCADE ON DELETE RESTRICT
, PRIMARY KEY(store_id, product_id)
, quantity integer NOT NULL CHECK(quantity > 0)
);
--
-- Another schema with
--
CREATE SCHEMA warehouse
CREATE TABLE warehouse
( warehouse_id SERIAL PRIMARY KEY
, warehouse_code text NOT NULL UNIQUE
CHECK(warehouse_code = upper(warehouse_code))
, warehouse_manager text NOT NULL
, warehouse_supervisor text UNIQUE
, warehouse_description text
, CHECK (upper(warehouse_manager) != upper(warehouse_supervisor))
)
CREATE TABLE inventory
( warehouse_id integer REFERENCES warehouse
ON UPDATE CASCADE
ON DELETE RESTRICT
, product_id integer REFERENCES product.product
ON UPDATE CASCADE
ON DELETE RESTRICT
, PRIMARY KEY(warehouse_id, product_id)
, quantity integer NOT NULL
CHECK(quantity > 0)
)
CREATE VIEW products AS
SELECT DISTINCT product.*
FROM inventory
JOIN product.product USING (product_id);
-- Sample index
CREATE INDEX quantity_index ON warehouse.inventory (quantity);
--
-- Simple text comments
--
--COMMENT ON DATABASE IS
--'This database has been created for the purpose of simple
-- tests on PostgreSQL Autodoc.';
COMMENT ON SCHEMA product IS
'This schema stores a list of products and information
about the product';
COMMENT ON SCHEMA warehouse IS
'A list of warehouses and information on warehouses';
COMMENT ON TABLE warehouse.inventory IS
'Warehouse inventory';
COMMENT ON TABLE store.inventory IS
'Store inventory';
COMMENT ON COLUMN warehouse.warehouse.warehouse_code IS
'Internal code which represents warehouses for
invoice purposes';
COMMENT ON COLUMN warehouse.warehouse.warehouse_supervisor IS
'Supervisors name for a warehouse when one
has been assigned. The same supervisor may not
be assigned to more than one warehouse, per company
policy XYZ.';
COMMENT ON COLUMN warehouse.warehouse.warehouse_manager IS
'Name of Warehouse Manager';
--
-- A few simple functions
--
CREATE FUNCTION product.worker(integer, integer) RETURNS integer AS
'SELECT $1 + $1;' LANGUAGE sql;
CREATE FUNCTION warehouse.worker(integer, integer) RETURNS integer AS
'SELECT $1 * $1;' LANGUAGE sql;
COMMENT ON FUNCTION product.worker(integer, integer) IS
'Worker function appropriate for products';
COMMENT ON FUNCTION warehouse.worker(integer, integer) IS
'Worker function appropriate for warehouses.';
--
-- Inheritance
--
CREATE SCHEMA inherit
CREATE TABLE taba (cola integer)
CREATE TABLE tabb (colb integer) inherits(taba)
CREATE TABLE tab1 (col1 integer)
CREATE TABLE tab1b (col1b integer) inherits(tab1, tabb);
COMMIT;
+5
View File
@@ -0,0 +1,5 @@
CREATE TABLE film (
id INTEGER NOT NULL PRIMARY KEY,
title VARCHAR(32) NOT NULL,
release_year INTEGER NOT NULL CHECK (release_year >= 1888)
);
+3
View File
@@ -0,0 +1,3 @@
ALTER TABLE film ADD COLUMN length SMALLINT
CHECK (length > 0 AND length < 10000);
ALTER TABLE film ADD COLUMN rating CHAR(5);
+8
View File
@@ -0,0 +1,8 @@
CREATE TABLE language (
language_id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(20) NOT NULL,
last_update TIMESTAMP WITH TIME ZONE NOT NULL
);
ALTER TABLE film ADD COLUMN language_id INTEGER NOT NULL;
ALTER TABLE film ADD FOREIGN KEY (language_id)
REFERENCES language (language_id);
+34
View File
@@ -0,0 +1,34 @@
CREATE TABLE genre (
genre_id INTEGER PRIMARY KEY,
name VARCHAR(25) NOT NULL
);
INSERT INTO genre VALUES
(1, 'Action'),
(2, 'Adventure'),
(3, 'Animation'),
(4, 'Biography'),
(5, 'Comedy'),
(6, 'Crime'),
(7, 'Documentary'),
(8, 'Drama'),
(9, 'Family'),
(10, 'Fantasy'),
(11, 'Film-Noir'),
(12, 'History'),
(13, 'Horror'),
(14, 'Music'),
(15, 'Musical'),
(16, 'Mystery'),
(17, 'Romance'),
(18, 'Sci-Fi'),
(19, 'Sport'),
(20, 'Thriller'),
(21, 'War'),
(22, 'Western');
CREATE TABLE film_genre (
film_id INTEGER NOT NULL REFERENCES film (id),
genre_id INTEGER NOT NULL
REFERENCES genre (genre_id),
last_update TIMESTAMP WITH TIME ZONE NOT NULL,
PRIMARY KEY (film_id, genre_id)
);
File diff suppressed because it is too large Load Diff
+69
View File
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
"""Test dbtoyaml and yamltodb using autodoc schema
See http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/~checkout~/autodoc/autodoc/
regressdatabase.sql?rev=1.2
"""
from pyrseas.testutils import DbMigrateTestCase
class AutodocTestCase(DbMigrateTestCase):
def setUp(self):
super(DbMigrateTestCase, self).setUp()
self.add_public_schema(self.srcdb)
self.add_public_schema(self.db)
@classmethod
def tearDown(cls):
cls.remove_tempfiles('autodoc')
cls.remove_tempfiles('empty')
def test_autodoc(self):
# Create the source schema
self.execute_script(__file__, 'autodoc-schema.sql')
# Run pg_dump against source database
srcdump = self.tempfile_path('autodoc-src.dump')
self.run_pg_dump(srcdump, True)
# Create source YAML file
srcyaml = self.tempfile_path('autodoc-src.yaml')
self.create_yaml(srcyaml, True)
# Run pg_dump/dbtoyaml against empty target database
emptydump = self.tempfile_path('empty.dump')
self.run_pg_dump(emptydump)
emptyyaml = self.tempfile_path('empty.yaml')
self.create_yaml(emptyyaml)
# Migrate the target database
targsql = self.tempfile_path('autodoc.sql')
self.migrate_target(srcyaml, targsql)
# Run pg_dump against target database
targdump = self.tempfile_path('autodoc.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('autodoc.yaml')
self.create_yaml(targyaml)
# diff autodoc-src.dump against autodoc.dump
assert self.lines(srcdump) == self.lines(targdump)
# diff autodoc-src.yaml against autodoc.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
# Undo the changes
self.migrate_target(emptyyaml, targsql)
# Run pg_dump against target database
self.run_pg_dump(targdump)
# Create target YAML file
self.create_yaml(targyaml)
# diff empty.dump against autodoc.dump
assert self.lines(emptydump) == self.lines(targdump)
# diff empty.yaml against autodoc.yaml
assert self.lines(emptyyaml) == self.lines(targyaml)
+81
View File
@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""Test dbtoyaml and yamltodb using autodoc schema but I/O to/from a directory
Same as test_autodoc.py but with directory tree instead of a single YAML file.
See http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/~checkout~/autodoc/autodoc/
regressdatabase.sql?rev=1.2
"""
from pyrseas.testutils import DbMigrateTestCase
class AutodocTestCase(DbMigrateTestCase):
def setUp(self):
super(DbMigrateTestCase, self).setUp()
self.add_public_schema(self.srcdb)
self.add_public_schema(self.db)
self.remove_tempfiles('metadata')
@classmethod
def tearDown(cls):
cls.remove_tempfiles('autodoc')
cls.remove_tempfiles('metadata')
def test_autodoc(self):
# Create the source schema
self.execute_script(__file__, 'autodoc-schema.sql')
# Run pg_dump against source database
srcdump = self.tempfile_path('autodoc-src.dump')
self.run_pg_dump(srcdump, True)
# Create source YAML file and directory tree
# Note: the single YAML file is for verification against the target,
# the YAML directory tree is for processing
srcyaml = self.tempfile_path('autodoc-src.yaml')
self.create_yaml(srcyaml, True)
self.create_yaml(None, True)
# Migrate the target database
targsql = self.tempfile_path('autodoc.sql')
self.migrate_target(None, targsql)
# Run pg_dump against target database
targdump = self.tempfile_path('autodoc.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('autodoc.yaml')
self.create_yaml(targyaml)
# diff autodoc-src.dump against autodoc.dump
assert self.lines(srcdump) == self.lines(targdump)
# diff autodoc-src.yaml against autodoc.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
# Undo the changes
self.srcdb.execute_commit(
"DROP SCHEMA inherit, product, store, warehouse CASCADE")
# Create source YAML file and directory tree
srcyaml = self.tempfile_path('autodoc-src-empty.yaml')
self.create_yaml(srcyaml, True)
self.create_yaml(None, True)
targsql = self.tempfile_path('autodoc-empty.sql')
self.migrate_target(None, targsql)
# Run pg_dump against source database
srcdump = self.tempfile_path('autodoc-src-empty.dump')
self.run_pg_dump(srcdump, True)
# Run pg_dump against target database
targdump = self.tempfile_path('autodoc-empty.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('autodoc-empty.yaml')
self.create_yaml(targyaml)
# diff empty.dump against autodoc.dump
assert self.lines(srcdump) == self.lines(targdump)
# diff empty.yaml against autodoc.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
+175
View File
@@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
"""Test dbtoyaml and yamltodb using film versions schema
See https://pyrseas.wordpress.com/2011/02/07/
version-control-part-2-sql-databases/
"""
import os
from pyrseas.testutils import DbMigrateTestCase
from pyrseas.yamlutil import yamldump
class FilmTestCase(DbMigrateTestCase):
def setUp(self):
self.remove_public_schema(self.srcdb)
@classmethod
def tearDownClass(cls):
cls.remove_tempfiles('film-0.')
cls.remove_tempfiles('usercfg.yaml')
cls.remove_tempfiles('config.yaml')
cls.remove_tempfiles('metadata')
def test_film_version_01(self):
"Create schema version 0.1"
self.execute_script(__file__, 'film-schema-0.1.sql')
# Run pg_dump against source database
srcdump = self.tempfile_path('film-0.1-src.dump')
self.run_pg_dump(srcdump, True)
# Create source YAML file
srcyaml = self.tempfile_path('film-0.1-src.yaml')
self.create_yaml(srcyaml, True)
# Run pg_dump/dbtoyaml against empty target database
self.run_pg_dump(self.tempfile_path('film-0.0.dump'))
self.create_yaml(self.tempfile_path('film-0.0.yaml'))
# Migrate the target database
self.migrate_target(srcyaml, self.tempfile_path('film-0.1.sql'))
# Run pg_dump against target database
targdump = self.tempfile_path('film-0.1.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('film-0.1.yaml')
self.create_yaml(targyaml)
# diff film-0.1-src.dump against film-0.1.dump
assert self.lines(srcdump) == self.lines(targdump)
# diff film-0.1-src.yaml against film-0.1.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
def test_film_version_02(self):
"Update schema to version 0.2"
self.execute_script(__file__, 'film-schema-0.2.sql')
# Run pg_dump against source database
srcdump = self.tempfile_path('film-0.2-src.dump')
self.run_pg_dump(srcdump, True)
# Create source YAML file
srcyaml = self.tempfile_path('film-0.2-src.yaml')
self.create_yaml(srcyaml, True)
# Migrate the target database
self.migrate_target(srcyaml, self.tempfile_path('film-0.2.sql'))
# Run pg_dump against target database
targdump = self.tempfile_path('film-0.2.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('film-0.2.yaml')
self.create_yaml(targyaml)
# diff film-0.2-src.dump against film-0.2.dump
assert self.lines(srcdump) == self.lines(targdump)
# diff film-0.2-src.yaml against film-0.2.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
def test_film_version_03(self):
"Update schema to version 0.3"
self.execute_script(__file__, 'film-schema-0.3a.sql')
self.execute_script(__file__, 'film-schema-0.3b.sql')
# Run pg_dump against source database
srcdump = self.tempfile_path('film-0.3-src.dump')
self.run_pg_dump(srcdump, True, True)
# Create source YAML file
usercfg = self.tempfile_path("usercfg.yaml")
with open(usercfg, 'w') as f:
f.write(yamldump({'repository': {'path': self.tempfile_path('')}}))
os.environ["PYRSEAS_USER_CONFIG"] = usercfg
with open(self.tempfile_path("config.yaml"), 'w') as f:
f.write(yamldump({'datacopy': {'schema sd': ['genre']}}))
srcyaml = self.tempfile_path('film-0.3-src.yaml')
self.create_yaml(srcyaml, True)
# Migrate the target database
self.migrate_target(srcyaml, self.tempfile_path('film-0.3.sql'))
# Run pg_dump against target database
targdump = self.tempfile_path('film-0.3.dump')
self.run_pg_dump(targdump, False, True)
# Create target YAML file
targyaml = self.tempfile_path('film-0.3.yaml')
self.create_yaml(targyaml)
# diff film-0.3-src.dump against film-0.3.dump
assert self.lines(srcdump) == self.lines(targdump)
# diff film-0.3-src.yaml against film-0.3.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
def test_film_version_04(self):
"Revert to schema version 0.2"
srcyaml = self.tempfile_path('film-0.2.yaml')
self.migrate_target(srcyaml, self.tempfile_path('film-0.3-undo.sql'))
# Run pg_dump against target database
targdump = self.tempfile_path('film-0.3-undo.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('film-0.3-undo.yaml')
self.create_yaml(targyaml)
# diff film-0.2.dump against film-0.3-undo.dump
srcdump = self.tempfile_path('film-0.2.dump')
assert self.lines(srcdump) == self.lines(targdump)
# diff film-0.2.yaml against film-0.3-undo.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
def test_film_version_05(self):
"Revert to schema version 0.1"
srcyaml = self.tempfile_path('film-0.1.yaml')
self.migrate_target(srcyaml, self.tempfile_path('film-0.2-undo.sql'))
# Run pg_dump against target database
targdump = self.tempfile_path('film-0.2-undo.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('film-0.2-undo.yaml')
self.create_yaml(targyaml)
# diff film-0.1.dump against film-0.2-undo.dump
srcdump = self.tempfile_path('film-0.1.dump')
assert self.lines(srcdump) == self.lines(targdump)
# diff film-0.1.yaml against film-0.2-undo.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
def test_film_version_06(self):
"Revert to empty schema"
srcyaml = self.tempfile_path('film-0.0.yaml')
self.migrate_target(srcyaml, self.tempfile_path('film-0.1-undo.sql'))
# Run pg_dump against target database
targdump = self.tempfile_path('film-0.1-undo.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('film-0.1-undo.yaml')
self.create_yaml(targyaml)
# diff film-0.0.dump against film-0.1-undo.dump
srcdump = self.tempfile_path('film-0.0.dump')
assert self.lines(srcdump) == self.lines(targdump)
# diff film-0.0.yaml against film-0.1-undo.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
+88
View File
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
"""Test dbtoyaml and yamltodb using pagila schema
See http://cvs.pgfoundry.org/cgi-bin/cvsweb.cgi/dbsamples/pagila/
pagila-schema.sql?rev=1.8
"""
from difflib import unified_diff
from pyrseas.testutils import DbMigrateTestCase
class PagilaTestCase(DbMigrateTestCase):
def setUp(self):
super(DbMigrateTestCase, self).setUp()
self.add_public_schema(self.srcdb)
self.add_public_schema(self.db)
@classmethod
def tearDown(cls):
cls.remove_tempfiles('pagila')
cls.remove_tempfiles('empty')
def test_pagila(self):
# Create the source schema
self.execute_script(__file__, 'pagila-schema.sql')
# Run pg_dump against source database
srcdump = self.tempfile_path('pagila-src.dump')
self.run_pg_dump(srcdump, True)
# Create source YAML file
srcyaml = self.tempfile_path('pagila-src.yaml')
self.create_yaml(srcyaml, True)
# Run pg_dump/dbtoyaml against empty target database
emptydump = self.tempfile_path('empty.dump')
self.run_pg_dump(emptydump)
emptyyaml = self.tempfile_path('empty.yaml')
self.create_yaml(emptyyaml)
# Migrate the target database
targsql = self.tempfile_path('pagila.sql')
self.migrate_target(srcyaml, targsql)
# Run pg_dump against target database
targdump = self.tempfile_path('pagila.dump')
self.run_pg_dump(targdump)
# Create target YAML file
targyaml = self.tempfile_path('pagila.yaml')
self.create_yaml(targyaml)
# diff pagila-src.dump against pagila.dump
# order of triggers requires special handling
adds = []
subs = []
for line in unified_diff(self.lines(srcdump), self.lines(targdump)):
if line == '--- \n' or line == '+++ \n' or line.startswith('@@'):
continue
if line[:1] == '+':
adds.append(line[1:-1])
elif line[:1] == '-':
subs.append(line[1:-1])
subs = sorted(subs)
for i, line in enumerate(sorted(adds)):
assert line == subs[i]
# diff pagila-src.yaml against pagila.yaml
assert self.lines(srcyaml) == self.lines(targyaml)
# Undo the changes
self.migrate_target(emptyyaml, targsql)
# Workaround problem with privileges on schema public
self.db.execute("GRANT ALL ON SCHEMA public TO postgres")
self.db.conn.commit()
# Run pg_dump against target database
self.run_pg_dump(targdump)
self.db.execute("REVOKE ALL ON SCHEMA public FROM postgres")
self.db.conn.commit()
# Create target YAML file
self.create_yaml(targyaml)
# diff empty.dump against pagila.dump
assert self.lines(emptydump) == self.lines(targdump)
# diff empty.yaml against pagila.yaml
assert self.lines(emptyyaml) == self.lines(targyaml)
+81
View File
@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""Test configuration files"""
import os
import sys
from pyrseas.config import Config
from pyrseas.cmdargs import cmd_parser, parse_args
from pyrseas.yamlutil import yamldump
USER_CFG_DATA = {'database': {'port': 5433},
'output': {'version_comment': True}}
CFG_TABLE_DATA = {'schema public': ['t1', 't2']}
CFG_DATA = {'datacopy': CFG_TABLE_DATA}
CFG_FILE = 'testcfg.yaml'
def test_defaults():
"Create a configuration with defaults"
cfg = Config()
for key in ['audit_columns', 'functions', 'function_templates', 'columns',
'triggers']:
assert key in cfg['augmenter']
for key in ['metadata', 'data']:
assert key in cfg['repository']
def test_user_config(tmpdir):
"Test a user configuration file"
f = tmpdir.join(CFG_FILE)
f.write(yamldump(USER_CFG_DATA))
os.environ["PYRSEAS_USER_CONFIG"] = f.strpath
cfg = Config()
assert cfg['database'] == {'port': 5433}
assert cfg['output'] == {'version_comment': True}
def test_repo_config(tmpdir):
"Test a repository configuration file"
ucfg = tmpdir.join(CFG_FILE)
ucfg.write(yamldump({'repository': {'path': tmpdir.strpath}}))
f = tmpdir.join("config.yaml")
f.write(yamldump(CFG_DATA))
os.environ["PYRSEAS_USER_CONFIG"] = ucfg.strpath
cfg = Config()
assert cfg['datacopy'] == CFG_TABLE_DATA
def test_cmd_parser(tmpdir):
"Test parsing a configuration file specified on the command line"
f = tmpdir.join(CFG_FILE)
f.write(yamldump(CFG_DATA))
sys.argv = ['testprog', 'testdb', '--config', f.strpath]
os.environ["PYRSEAS_USER_CONFIG"] = ''
parser = cmd_parser("Test description", '0.0.1')
cfg = parse_args(parser)
assert cfg['datacopy'] == CFG_TABLE_DATA
def test_parse_repo_config(tmpdir):
"Test parsing a repository configuration file in the current directory"
f = tmpdir.join('config.yaml')
f.write(yamldump(CFG_DATA))
os.chdir(tmpdir.strpath)
sys.argv = ['testprog', 'testdb']
os.environ["PYRSEAS_USER_CONFIG"] = ''
parser = cmd_parser("Test description", '0.0.1')
cfg = parse_args(parser)
assert cfg['datacopy'] == CFG_TABLE_DATA
def test_repo_user_config(tmpdir):
"Test a repository path specified in the user config"
usercfg = {'repository': {'path': tmpdir.strpath}}
userf = tmpdir.join("usercfg.yaml")
userf.write(yamldump(usercfg))
os.environ["PYRSEAS_USER_CONFIG"] = userf.strpath
repof = tmpdir.join("config.yaml")
repof.write(yamldump(CFG_DATA))
cfg = Config()
assert cfg['datacopy'] == CFG_TABLE_DATA