initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# to avoid setuptools/distutils bug
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
"""Pyrseas dbobject unit tests"""
|
||||
@@ -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)"
|
||||
@@ -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")'
|
||||
@@ -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
@@ -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'"]
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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"
|
||||
@@ -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)"
|
||||
@@ -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)"]
|
||||
@@ -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)"]
|
||||
@@ -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 +'"]
|
||||
@@ -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'"]
|
||||
@@ -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'"]
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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'"]
|
||||
@@ -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]
|
||||
@@ -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"
|
||||
@@ -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)"
|
||||
@@ -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"]
|
||||
@@ -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"
|
||||
@@ -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]
|
||||
@@ -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()"
|
||||
@@ -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)")
|
||||
@@ -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'"]
|
||||
@@ -0,0 +1 @@
|
||||
"""Pyrseas functional tests"""
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user