Creating the Operators and Support Routines

PostgreSQL 7.2 Documentation
Prev | Chapter 17. Interfacing Extensions To Indexes | Next

# 17.6. Creating the Operators and Support Routines

So now we have an access method and an operator class. We still need a set of operators. The procedure for defining operators was discussed in Chapter 14. For the `complex_abs_ops` operator class on B-trees, the operators we require are:

- absolute-value less-than (strategy 1)
- absolute-value less-than-or-equal (strategy 2)
- absolute-value equal (strategy 3)
- absolute-value greater-than-or-equal (strategy 4)
- absolute-value greater-than (strategy 5)

Suppose the code that implements these functions is stored in the file ``PGROOT`/src/tutorial/complex.c`, which we have compiled into ``PGROOT`/src/tutorial/complex.so`. Part of the C code looks like this:

    #define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y)

             bool
             complex_abs_eq(Complex *a, Complex *b)
             {
                 double amag = Mag(a), bmag = Mag(b);
                 return (amag==bmag);
             }

(Note that we will only show the equality operator for the rest of the examples. The other four operators are very similar. Refer to `complex.c` or `complex.source` for the details.)

We make the function known to PostgreSQL like this:

    CREATE FUNCTION complex_abs_eq(complex, complex) RETURNS boolean
        AS 'PGROOT/src/tutorial/complex'
        LANGUAGE C;

There are some important things that are happening here:

- First, note that operators for less-than, less-than-or-equal, equal, greater-than-or-equal, and greater-than for `complex` are being defined. We can only have one operator named, say, = and taking type `complex` for both operands. In this case we don't have any other operator = for `complex`, but if we were building a practical data type we'd probably want = to be the ordinary equality operation for complex numbers. In that case, we'd need to use some other operator name for `complex_abs_eq`.

- Second, although PostgreSQL can cope with operators having the same name as long as they have different input data types, C can only cope with one global routine having a given name, period. So we shouldn't name the C function something simple like `abs_eq`. Usually it's a good practice to include the data type name in the C function name, so as not to conflict with functions for other data types.

- Third, we could have made the PostgreSQL name of the function `abs_eq`, relying on PostgreSQL to distinguish it by input data types from any other PostgreSQL function of the same name. To keep the example simple, we make the function have the same names at the C level and PostgreSQL level.

- Finally, note that these operator functions return Boolean values. In practice, all operators defined as index access method strategies must return type `boolean`, since they must appear at the top level of a `WHERE` clause to be used with an index. (On the other hand, the support function returns whatever the particular access method expects -- in this case, a signed integer.)

The final routine in the file is the "support routine" mentioned when we discussed the `amsupport` column of the `pg_am` table. We will use this later on. For now, ignore it.

Now we are ready to define the operators:

    CREATE OPERATOR = (
         leftarg = complex, rightarg = complex,
         procedure = complex_abs_eq,
         restrict = eqsel, join = eqjoinsel
             );

The important things here are the procedure names (which are the C functions defined above) and the restriction and join selectivity functions. You should just use the selectivity functions used in the example (see `complex.source`). Note that there are different such functions for the less-than, equal, and greater-than cases. These must be supplied or the optimizer will be unable to make effective use of the index.

The next step is to add entries for these operators to the `pg_amop` relation. To do this, we'll need the OIDs of the operators we just defined. We'll look up the names of all the operators that take two operands of type `complex`, and pick ours out:

    SELECT o.oid AS opoid, o.oprname
        INTO TEMP TABLE complex_ops_tmp
        FROM pg_operator o, pg_type t
        WHERE o.oprleft = t.oid and o.oprright = t.oid
          and t.typname = 'complex';

     opoid  | oprname
    --------+---------
     277963 | +
     277970 | <
     277971 | <=
     277972 | =
     277973 | >=
     277974 | >
    (6 rows)

(Again, some of your OID numbers will almost certainly be different.) The operators we are interested in are those with OIDs 277970 through 277974. The values you get will probably be different, and you should substitute them for the values below. We will do this with a select statement.

Now we are ready to insert entries into `pg_amop` for our new operator class. These entries must associate the correct B-tree strategy numbers with each of the operators we need. The command to insert the less-than operator looks like:

    INSERT INTO pg_amop (amopclaid, amopstrategy, amopreqcheck, amopopr)
        SELECT opcl.oid, 1, false, c.opoid
            FROM pg_opclass opcl, complex_ops_tmp c
            WHERE
                opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
                opcname = 'complex_abs_ops' AND
                c.oprname = '<';

Now do this for the other operators substituting for the `1` in the second line above and the `<` in the last line. Note the order: "less than" is 1, "less than or equal" is 2, "equal" is 3, "greater than or equal" is 4, and "greater than" is 5.

The field `amopreqcheck` is not discussed here; it should always be false for B-tree operators.

The final step is the registration of the "support routine" previously described in our discussion of `pg_am`. The OID of this support routine is stored in the `pg_amproc` table, keyed by the operator class OID and the support routine number.

First, we need to register the function in PostgreSQL (recall that we put the C code that implements this routine in the bottom of the file in which we implemented the operator routines):

    CREATE FUNCTION complex_abs_cmp(complex, complex)
        RETURNS integer
        AS 'PGROOT/src/tutorial/complex'
        LANGUAGE C;

    SELECT oid, proname FROM pg_proc
        WHERE proname = 'complex_abs_cmp';

      oid   |     proname
    --------+-----------------
     277997 | complex_abs_cmp
    (1 row)

(Again, your OID number will probably be different.)

We can add the new row as follows:

    INSERT INTO pg_amproc (amopclaid, amprocnum, amproc)
        SELECT opcl.oid, 1, p.oid
            FROM pg_opclass opcl, pg_proc p
            WHERE
                opcamid = (SELECT oid FROM pg_am WHERE amname = 'btree') AND
                opcname = 'complex_abs_ops' AND
                p.proname = 'complex_abs_cmp';

And we're done! (Whew.) It should now be possible to create and use B-tree indexes on `complex` columns.

Prev | Home | Next
Operator Classes | Up | Index Cost Estimation Functions