Permalink
Browse files

Add support for operator families.

 * docs/index.rst: Add operfamily.rst.
 * docs/operfamily.rst: New page to document operator families.
 * pyrseas/database.py (Database.Dicts.__init__): Add dictionary for
   operator families.  (Database.link_refs): Add operator families
   argument to schemas.link_refs call.  (Database.diff_map): Call
   operfams.diff_map and operfams._drop.
 * pyrseas/dbobject/__init__.py (DbObject.{to_map, diff_map}): New
   generic methods.
 * pyrseas/dbobject/operfamily.py: New module to implement operator
   families.
 * pyrseas/dbobject/schema.py(Schema.to_map): Process operator
   families.  (SchemaDict.from_map): Process input operator families.
   (SchemaDict.link_refs): Add argument for operator families.
 * tests/dbobject/__init__.py: Invoke operator family tests.
 * tests/dbobject/test_operfamily.py: New test module.
 * tests/dbobject/utils.py (PostgresDb.clear): Drop existing operator
   families.
  • Loading branch information...
1 parent 25d9f69 commit 39ccee1ca1433dced6f1ee4585684a55d4f441b0 @jmafc jmafc committed Sep 7, 2011
View
@@ -51,6 +51,7 @@ classes and methods are documented mainly for developer use.
conversion
function
operator
+ operfamily
type
table
column
View
@@ -0,0 +1,36 @@
+Operator Families
+=================
+
+.. module:: pyrseas.dbobject.operfamily
+
+The :mod:`operfamily` module defines two classes: class
+:class:`OperatorFamily` derived from :class:`DbSchemaObject` and class
+:class:`OperatorFamilyDict` derived from :class:`DbObjectDict`.
+
+Operator Family
+---------------
+
+:class:`OperatorFamily` is derived from :class:`DbSchemaObject` and
+represents a `PostgreSQL operator family
+<http://www.postgresql.org/docs/current/static/sql-createopfamily.html>`_.
+
+.. autoclass:: OperatorFamily
+
+.. automethod:: OperatorFamily.extern_key
+
+.. automethod:: OperatorFamily.identifier
+
+.. automethod:: OperatorFamily.create
+
+Operator Family Dictionary
+--------------------------
+
+:class:`OperatorFamilyDict` is derived from
+:class:`~pyrseas.dbobject.DbObjectDict`. It is a dictionary that
+represents the collection of operator families in a database.
+
+.. autoclass:: OperatorFamilyDict
+
+.. automethod:: OperatorFamilyDict.from_map
+
+.. automethod:: OperatorFamilyDict.diff_map
View
@@ -20,6 +20,7 @@
from pyrseas.dbobject.index import IndexDict
from pyrseas.dbobject.function import ProcDict
from pyrseas.dbobject.operator import OperatorDict
+from pyrseas.dbobject.operfamily import OperatorFamilyDict
from pyrseas.dbobject.rule import RuleDict
from pyrseas.dbobject.trigger import TriggerDict
from pyrseas.dbobject.conversion import ConversionDict
@@ -56,6 +57,7 @@ def __init__(self, dbconn=None):
self.indexes = IndexDict(dbconn)
self.functions = ProcDict(dbconn)
self.operators = OperatorDict(dbconn)
+ self.operfams = OperatorFamilyDict(dbconn)
self.rules = RuleDict(dbconn)
self.triggers = TriggerDict(dbconn)
self.conversions = ConversionDict(dbconn)
@@ -72,7 +74,7 @@ def _link_refs(self, db):
"""Link related objects"""
db.languages.link_refs(db.functions)
db.schemas.link_refs(db.types, db.tables, db.functions, db.operators,
- db.conversions)
+ db.operfams, db.conversions)
db.tables.link_refs(db.columns, db.constraints, db.indexes,
db.rules, db.triggers)
db.types.link_refs(db.columns, db.constraints, db.functions)
@@ -150,6 +152,7 @@ def diff_map(self, input_map):
stmts.append(self.db.types.diff_map(self.ndb.types))
stmts.append(self.db.functions.diff_map(self.ndb.functions))
stmts.append(self.db.operators.diff_map(self.ndb.operators))
+ stmts.append(self.db.operfams.diff_map(self.ndb.operfams))
stmts.append(self.db.tables.diff_map(self.ndb.tables))
stmts.append(self.db.constraints.diff_map(self.ndb.constraints))
stmts.append(self.db.indexes.diff_map(self.ndb.indexes))
@@ -159,6 +162,7 @@ def diff_map(self, input_map):
stmts.append(self.db.conversions.diff_map(self.ndb.conversions))
stmts.append(self.db.casts.diff_map(self.ndb.casts))
stmts.append(self.db.operators._drop())
+ stmts.append(self.db.operfams._drop())
stmts.append(self.db.functions._drop())
stmts.append(self.db.types._drop())
stmts.append(self.db.schemas._drop())
@@ -92,6 +92,16 @@ def identifier(self):
"""
return quote_id(self.__dict__[self.keylist[0]])
+ def to_map(self):
+ """Convert an object to a YAML-suitable format
+
+ :return: dictionary
+ """
+ dct = self.__dict__.copy()
+ for k in self.keylist:
+ del dct[k]
+ return {self.extern_key(): dct}
+
def comment(self):
"""Return SQL statement to create COMMENT on object
@@ -119,6 +129,20 @@ def rename(self, newname):
"""
return "ALTER %s %s RENAME TO %s" % (self.objtype, self.name, newname)
+ def diff_map(self, inobj):
+ """Generate SQL to transform an existing object
+
+ :param inobj: a YAML map defining the new object
+ :return: list of SQL statements
+
+ Compares the object to an input object and generates SQL
+ statements to transform it into the one represented by the
+ input.
+ """
+ stmts = []
+ stmts.append(self.diff_description(inobj))
+ return stmts
+
def diff_description(self, inobj):
"""Generate SQL statements to add or change COMMENTs
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+"""
+ pyrseas.operfamily
+ ~~~~~~~~~~~~~~~~~~
+
+ This module defines two classes: OperatorFamily derived from
+ DbSchemaObject and OperatorFamilyDict derived from DbObjectDict.
+"""
+from pyrseas.dbobject import DbObjectDict, DbSchemaObject, quote_id
+
+
+class OperatorFamily(DbSchemaObject):
+ """An operator family"""
+
+ objtype = "OPERATOR FAMILY"
+
+ keylist = ['schema', 'name', 'index_method']
+
+ def extern_key(self):
+ """Return the key to be used in external maps for the operator family
+
+ :return: string
+ """
+ return '%s %s using %s' % (self.objtype.lower(), self.name,
+ self.index_method)
+
+ def identifier(self):
+ """Return a full identifier for an operator family object
+
+ :return: string
+ """
+ return "%s USING %s" % (self.qualname(), self.index_method)
+
+ def create(self):
+ """Return SQL statements to CREATE the operator family
+
+ :return: SQL statements
+ """
+ stmts = []
+ stmts.append("CREATE OPERATOR FAMILY %s USING %s" % (
+ self.qualname(), self.index_method))
+ if hasattr(self, 'description'):
+ stmts.append(self.comment())
+ return stmts
+
+
+class OperatorFamilyDict(DbObjectDict):
+ "The collection of operator families in a database"
+
+ cls = OperatorFamily
+ query = \
+ """SELECT nspname AS schema, opfname AS name,
+ amname AS index_method, description
+ FROM pg_opfamily o
+ JOIN pg_am a ON (opfmethod = a.oid)
+ JOIN pg_namespace n ON (opfnamespace = n.oid)
+ LEFT JOIN pg_description d
+ ON (o.oid = d.objoid AND d.objsubid = 0)
+ WHERE (nspname != 'pg_catalog' AND nspname != 'information_schema')
+ ORDER BY opfnamespace, opfname, amname"""
+
+ def from_map(self, schema, inopfams):
+ """Initalize the dict of operator families by converting the input map
+
+ :param schema: schema owning the operators
+ :param inopfams: YAML map defining the operator families
+ """
+ for key in inopfams.keys():
+ if not key.startswith('operator family ') or not ' using ' in key:
+ raise KeyError("Unrecognized object type: %s" % key)
+ pos = key.rfind(' using ')
+ opf = key[16:pos] # 16 = len('operator family ')
+ idx = key[pos + 7:] # 7 = len(' using ')
+ inopfam = inopfams[key]
+ self[(schema.name, opf, idx)] = opfam = OperatorFamily(
+ schema=schema.name, name=opf, index_method=idx)
+ for attr, val in inopfam.items():
+ setattr(opfam, attr, val)
+ if 'oldname' in inopfam:
+ opfam.oldname = inopfam['oldname']
+ if 'description' in inopfam:
+ opfam.description = inopfam['description']
+
+ def diff_map(self, inopfams):
+ """Generate SQL to transform existing operator families
+
+ :param inopfams: a YAML map defining the new operator families
+ :return: list of SQL statements
+
+ Compares the existing operator family definitions, as fetched
+ from the catalogs, to the input map and generates SQL
+ statements to transform the operator families accordingly.
+ """
+ stmts = []
+ # check input operator families
+ for (sch, opf, idx) in inopfams.keys():
+ inopfam = inopfams[(sch, opf, idx)]
+ # does it exist in the database?
+ if (sch, opf, idx) not in self:
+ if not hasattr(inopfam, 'oldname'):
+ # create new operator family
+ stmts.append(inopfam.create())
+ else:
+ stmts.append(self[(sch, opf, idx)].rename(inopfam))
+ else:
+ # check operator family objects
+ stmts.append(self[(sch, opf, idx)].diff_map(inopfam))
+
+ # check existing operator families
+ for (sch, opf, idx) in self.keys():
+ oper = self[(sch, opf, idx)]
+ # if missing, mark it for dropping
+ if (sch, opf, idx) not in inopfams:
+ oper.dropped = False
+
+ return stmts
+
+ def _drop(self):
+ """Actually drop the operator families
+
+ :return: SQL statements
+ """
+ stmts = []
+ for (sch, opf, idx) in self.keys():
+ oper = self[(sch, opf, idx)]
+ if hasattr(oper, 'dropped'):
+ stmts.append(oper.drop())
+ return stmts
@@ -62,6 +62,11 @@ def to_map(self, dbschemas):
for oper in self.operators.keys():
operators.update(self.operators[oper].to_map())
schema[key].update(operators)
+ if hasattr(self, 'operfams'):
+ operfams = {}
+ for opf in self.operfams.keys():
+ operfams.update(self.operfams[opf].to_map())
+ schema[key].update(operfams)
if hasattr(self, 'conversions'):
conversions = {}
for conv in self.conversions.keys():
@@ -139,6 +144,7 @@ def from_map(self, inmap, newdb):
intables = {}
infuncs = {}
inopers = {}
+ inopfams = {}
inconvs = {}
for key in inschema.keys():
if key.startswith('domain '):
@@ -151,6 +157,8 @@ def from_map(self, inmap, newdb):
elif key.startswith('function ') \
or key.startswith('aggregate '):
infuncs.update({key: inschema[key]})
+ elif key.startswith('operator family'):
+ inopfams.update({key: inschema[key]})
elif key.startswith('operator '):
inopers.update({key: inschema[key]})
elif key.startswith('conversion '):
@@ -166,15 +174,18 @@ def from_map(self, inmap, newdb):
newdb.tables.from_map(schema, intables, newdb)
newdb.functions.from_map(schema, infuncs)
newdb.operators.from_map(schema, inopers)
+ newdb.operfams.from_map(schema, inopfams)
newdb.conversions.from_map(schema, inconvs)
- def link_refs(self, dbtypes, dbtables, dbfunctions, dbopers, dbconvs):
+ def link_refs(self, dbtypes, dbtables, dbfunctions, dbopers, dbopfams,
+ dbconvs):
"""Connect types, tables and functions to their respective schemas
:param dbtypes: dictionary of types and domains
:param dbtables: dictionary of tables, sequences and views
:param dbfunctions: dictionary of functions
:param dbopers: dictionary of operators
+ :param dbopfams: dictionary of operator families
:param dbconvs: dictionary of conversions
Fills in the `domains` dictionary for each schema by
@@ -238,6 +249,13 @@ def link_refs(self, dbtypes, dbtables, dbfunctions, dbopers, dbconvs):
if not hasattr(schema, 'operators'):
schema.operators = {}
schema.operators.update({(opr, lft, rgt): oper})
+ for (sch, opf, idx) in dbopfams.keys():
+ opfam = dbopfams[(sch, opf, idx)]
+ assert self[sch]
+ schema = self[sch]
+ if not hasattr(schema, 'operfams'):
+ schema.operfams = {}
+ schema.operfams.update({(opf, idx): opfam})
for (sch, cnv) in dbconvs.keys():
conv = dbconvs[(sch, cnv)]
assert self[sch]
@@ -15,6 +15,7 @@
import test_view
import test_function
import test_operator
+import test_operfamily
import test_trigger
import test_rule
import test_conversion
@@ -34,6 +35,7 @@ def suite():
tests.addTest(test_view.suite())
tests.addTest(test_function.suite())
tests.addTest(test_operator.suite())
+ tests.addTest(test_operfamily.suite())
tests.addTest(test_trigger.suite())
tests.addTest(test_rule.suite())
tests.addTest(test_conversion.suite())
Oops, something went wrong.

0 comments on commit 39ccee1

Please sign in to comment.