xindex.sgml
上传用户:blenddy
上传日期:2007-01-07
资源大小:6495k
文件大小:22k
源码类别:

数据库系统

开发平台:

Unix_Linux

  1.  <chapter id="xindex">
  2.   <title>Interfacing Extensions To Indices</title>
  3.   <para>
  4.    The procedures described thus far let you define a new type, new
  5.    functions and new operators.  However, we cannot yet define a secondary
  6.    index (such as a <acronym>B-tree</acronym>, <acronym>R-tree</acronym> or
  7.    hash access method) over a new type or its operators.
  8.   </para>
  9.   <para>
  10.    Look back at
  11.    <xref endterm="EXTEND-CATALOGS" linkend="EXTEND-CATALOGS">.
  12.    The right half shows the  catalogs  that we must modify in order to tell
  13.    <productname>Postgres</productname> how to use a user-defined type and/or
  14.    user-defined  operators with an index (i.e., <filename>pg_am, pg_amop,
  15.     pg_amproc, pg_operator</filename> and <filename>pg_opclass</filename>).
  16.    Unfortunately, there is no simple command to do this.  We will demonstrate
  17.    how to modify these catalogs through a running example:  a  new  operator
  18.    class for the <acronym>B-tree</acronym> access method that stores and
  19.    sorts complex numbers in ascending absolute value order.
  20.   </para>
  21.   <para>
  22.    The <filename>pg_am</filename> class contains one instance for every user
  23.    defined access method.  Support for the heap access method is built into
  24.    <productname>Postgres</productname>, but every other access method is
  25.    described here.  The schema is
  26.    <table tocentry="1">
  27.     <title>Index Schema</title>
  28.     <titleabbrev>Indices</titleabbrev>
  29.     <tgroup cols="2">
  30.      <thead>
  31.       <row>
  32.        <entry>Attribute</entry>
  33.        <entry>Description</entry>
  34.       </row>
  35.      </thead>
  36.      <tbody>
  37.       <row>
  38.        <entry>amname</entry>
  39.        <entry>name of the access method</entry>
  40.       </row>
  41.       <row>
  42.        <entry>amowner</entry>
  43.        <entry>object id of the owner's instance in pg_user</entry>
  44.       </row>
  45.       <row>
  46.        <entry>amkind</entry>
  47.        <entry>not used at present, but set to 'o' as a place holder</entry>
  48.       </row>
  49.       <row>
  50.        <entry>amstrategies</entry>
  51.        <entry>number of strategies for this access method (see below)</entry>
  52.       </row>
  53.       <row>
  54.        <entry>amsupport</entry>
  55.        <entry>number of support routines for this access method (see below)</entry>
  56.       </row>
  57.       <row>
  58.        <entry>amgettuple</entry>
  59.       </row>
  60.       <row>
  61.        <entry>aminsert</entry>
  62.       </row>
  63.       <row>
  64.        <entry>...</entry>
  65.        <entry>procedure  identifiers  for  interface routines to the access
  66. method.  For example, regproc ids for opening,  closing,  and
  67. getting instances from the access method appear here.</entry>
  68.       </row>
  69.      </tbody>
  70.     </tgroup>
  71.    </table>
  72.   </para>
  73.   <para>
  74.    The <acronym>object ID</acronym> of the instance in
  75.    <filename>pg_am</filename> is used as a foreign key in lots of other
  76.    classes.  You  don't  need to  add a new instance to this class; all
  77.    you're interested in is the <acronym>object ID</acronym> of the access
  78.    method instance you want to extend:
  79.    <programlisting>
  80. SELECT oid FROM pg_am WHERE amname = 'btree';
  81.          +----+
  82.          |oid |
  83.          +----+
  84.          |403 |
  85.          +----+
  86.    </programlisting>
  87.    We will use that <command>SELECT</command> in a <command>WHERE</command>
  88.    clause later.
  89.   </para>
  90.   <para>
  91.    The <filename>amstrategies</filename> attribute exists to standardize
  92.    comparisons across data types.  For example, <acronym>B-tree</acronym>s
  93.    impose a strict ordering on keys, lesser to greater.  Since
  94.    <productname>Postgres</productname> allows the user to define operators,
  95.    <productname>Postgres</productname> cannot look at the name of an operator
  96.    (eg, ">" or "<") and tell what kind of comparison it is.  In fact,
  97.    some  access methods don't impose any ordering at all.  For example,
  98.    <acronym>R-tree</acronym>s express a rectangle-containment relationship,
  99.    whereas a hashed data structure expresses only bitwise similarity based
  100.    on the value of a hash function.  <productname>Postgres</productname>
  101.    needs some consistent way of taking a qualification in your query,
  102.    looking at the operator and then deciding if a usable index exists.  This
  103.    implies that <productname>Postgres</productname> needs to know, for
  104.    example, that the  "<="  and  ">" operators partition a
  105.    <acronym>B-tree</acronym>.  <productname>Postgres</productname>
  106.    uses strategies to express these relationships  between
  107.    operators and the way they can be used to scan indices.
  108.   </para>
  109.   <para>
  110.    Defining a new set of strategies is beyond the scope of this discussion,
  111.    but we'll explain how <acronym>B-tree</acronym> strategies work because
  112.    you'll need to know that to add a new operator class. In the
  113.    <filename>pg_am</filename> class, the amstrategies attribute is the
  114.    number of strategies defined for this access method. For
  115.    <acronym>B-tree</acronym>s, this number is 5.  These strategies
  116.    correspond to
  117.    <table tocentry="1">
  118.     <title>B-tree Strategies</title>
  119.     <titleabbrev>B-tree</titleabbrev>
  120.     <tgroup cols="2">
  121.      <thead>
  122.       <row>
  123.        <entry>Operation</entry>
  124.        <entry>Index</entry>
  125.       </row>
  126.      </thead>
  127.      <tbody>
  128.       <row>
  129.        <entry>less than</entry>
  130.        <entry>1</entry>
  131.       </row>
  132.       <row>
  133.        <entry>less than or equal</entry>
  134.        <entry>2</entry>
  135.       </row>
  136.       <row>
  137.        <entry>equal</entry>
  138.        <entry>3</entry>
  139.       </row>
  140.       <row>
  141.        <entry>greater than or equal</entry>
  142.        <entry>4</entry>
  143.       </row>
  144.       <row>
  145.        <entry>greater than</entry>
  146.        <entry>5</entry>
  147.       </row>
  148.      </tbody>
  149.     </tgroup>
  150.    </table>
  151.   </para>
  152.   <para>
  153.    The idea is that you'll need to add procedures corresponding to the
  154.    comparisons above to the <filename>pg_amop</filename> relation (see below).
  155.    The access method code can use these strategy numbers, regardless of data
  156.    type, to figure out how to partition the <acronym>B-tree</acronym>,
  157.    compute selectivity, and so on.  Don't worry about the details of adding
  158.    procedures yet; just understand that there must be a set of these
  159.    procedures for <filename>int2, int4, oid,</filename> and every other
  160.    data type on which a <acronym>B-tree</acronym> can operate.
  161.   </para>
  162.   <para>
  163.    Sometimes, strategies aren't enough information for the system to figure
  164.    out how to use an index.  Some access methods require other support
  165.    routines in order to work. For example, the <acronym>B-tree</acronym>
  166.    access method must be able to compare two keys and determine whether one
  167.    is greater than, equal to, or less than the other.  Similarly, the
  168.    <acronym>R-tree</acronym> access method must be able to compute
  169.    intersections,  unions, and sizes of rectangles.  These
  170.    operations do not correspond to user qualifications in
  171.    SQL queries;  they are administrative routines used by
  172.    the access methods, internally.
  173.   </para>
  174.   <para>
  175.    In order to manage diverse support routines consistently across all
  176.    <productname>Postgres</productname> access methods,
  177.    <filename>pg_am</filename> includes an attribute called
  178.    <filename>amsupport</filename>.  This attribute records the number of
  179.    support routines used by an access method.  For <acronym>B-tree</acronym>s,
  180.    this number is one -- the routine to take two keys and return -1, 0, or
  181.    +1, depending on whether the first key is less than, equal
  182.    to, or greater than the second.
  183.    <note>
  184.     <para>
  185.      Strictly  speaking, this routine can return a negative
  186.      number (< 0), 0, or a non-zero positive number (> 0).
  187.     </para>
  188.    </note>
  189.   </para>
  190.   <para>
  191.    The <filename>amstrategies</filename> entry in pg_am is just the number
  192.    of strategies defined for the access method in question.  The procedures
  193.    for less than, less equal, and so on don't appear in
  194.    <filename>pg_am</filename>.  Similarly, <filename>amsupport</filename>
  195.    is just the number of support routines required by  the  access
  196.    method.  The actual routines are listed elsewhere.
  197.   </para>
  198.   <para>
  199.    The next class of interest is pg_opclass.  This class exists only to
  200.    associate a name and default type with an oid.  In pg_amop, every
  201.    <acronym>B-tree</acronym> operator class has a set of procedures, one
  202.    through five, above. Some existing opclasses are <filename>int2_ops,
  203.     int4_ops, and oid_ops</filename>.  You need to add an instance with your
  204.    opclass name (for example, <filename>complex_abs_ops</filename>) to
  205.    <filename>pg_opclass</filename>.  The <filename>oid</filename> of
  206.    this instance is a foreign key in other classes.
  207.    <programlisting>
  208. INSERT INTO pg_opclass (opcname, opcdeftype)
  209.     SELECT 'complex_abs_ops', oid FROM pg_type WHERE typname = 'complex_abs';
  210. SELECT oid, opcname, opcdeftype
  211.     FROM pg_opclass
  212.     WHERE opcname = 'complex_abs_ops';
  213.          +------+-----------------+------------+
  214.          |oid   | opcname         | opcdeftype |
  215.          +------+-----------------+------------+
  216.          |17314 | complex_abs_ops |      29058 |
  217.          +------+-----------------+------------+
  218.    </programlisting>
  219.    Note that the oid for your <filename>pg_opclass</filename> instance will
  220.    be different!  Don't worry about this though.  We'll get this number
  221.    from the system later just like we got the oid of the type here.
  222.   </para>
  223.   <para>
  224.    So now we have an access method and an operator  class.
  225.    We  still  need  a  set of operators; the procedure for
  226.    defining operators was discussed earlier in  this  manual.
  227.    For  the  complex_abs_ops  operator  class on Btrees,
  228.    the operators we require are:
  229.    <programlisting>
  230.         absolute value less-than
  231.         absolute value less-than-or-equal
  232.         absolute value equal
  233.         absolute value greater-than-or-equal
  234.         absolute value greater-than
  235.    </programlisting>
  236.   </para>
  237.   <para>
  238.    Suppose the code that implements the functions  defined
  239.    is stored in the file
  240.    <filename>PGROOT/src/tutorial/complex.c</filename>
  241.   </para>
  242.   <para>
  243.    Part of the code look like this: (note that we will only show the
  244.    equality operator for the rest of the examples.  The other four
  245.    operators are very similar.  Refer to <filename>complex.c</filename>
  246.    or <filename>complex.source</filename> for the details.)
  247.    <programlisting>
  248. #define Mag(c) ((c)-&gt;x*(c)-&gt;x + (c)-&gt;y*(c)-&gt;y)
  249.          bool
  250.          complex_abs_eq(Complex *a, Complex *b)
  251.          {
  252.              double amag = Mag(a), bmag = Mag(b);
  253.              return (amag==bmag);
  254.          }
  255.    </programlisting>
  256.   </para>
  257.   <para>
  258.    There are a couple of important things that are happening below.
  259.   </para>
  260.   <para>
  261.    First, note that operators for less-than, less-than-or equal, equal,
  262.    greater-than-or-equal, and greater-than for <filename>int4</filename>
  263.    are being defined.  All of these operators are already defined for
  264.    <filename>int4</filename> under the names &lt;, &lt;=, =, &gt;=,
  265.    and &gt;. The new operators behave differently, of course.  In order
  266.    to guarantee that <productname>Postgres</productname> uses these
  267.    new operators rather than the old ones, they need to be named differently
  268.    from the old ones.  This is a key point: you can overload operators in
  269.    <productname>Postgres</productname>, but only if the operator isn't
  270.    already defined for the argument types.  That is, if you have &lt;
  271.    defined for (int4, int4), you can't define it again.
  272.    <productname>Postgres</productname> does not check this when you define
  273.    your operator, so be careful.  To avoid this problem, odd names will be
  274.    used for the operators.  If you get this wrong, the access methods
  275.    are likely to crash when you try to do scans.
  276.   </para>
  277.   <para>
  278.    The other important point is that all the operator functions return
  279.    Boolean values.  The access methods rely on this fact.  (On the other
  280.    hand, the support function returns whatever the particular access method
  281.    expects -- in this case, a signed integer.) The final routine in the
  282.    file is the "support routine" mentioned when we discussed the amsupport
  283.    attribute of the <filename>pg_am</filename> class.  We will use this
  284.    later on.  For now, ignore it.
  285.   </para>
  286.   <para>
  287.    <programlisting>
  288. CREATE FUNCTION complex_abs_eq(complex_abs, complex_abs)
  289.               RETURNS bool
  290.               AS 'PGROOT/tutorial/obj/complex.so'
  291.               LANGUAGE 'c';
  292.    </programlisting>
  293.   </para>
  294.   <para>
  295.    Now define the operators that use them.  As noted, the operator names
  296.    must be unique among all operators that take two <filename>int4</filename>
  297.    operands.  In order to see if the operator names listed below are taken,
  298.    we can do a query  on <filename>pg_operator</filename>:
  299.    <programlisting>
  300.     /*
  301.      * this query uses the regular expression operator (~)
  302.      * to find three-character operator names that end in
  303.      * the character &amp;
  304.      */
  305.     SELECT *
  306.      FROM pg_operator
  307.      WHERE oprname ~ '^..&amp;$'::text;
  308.    </programlisting>
  309.   </para>
  310.   <para>
  311.    to see if your name is taken for the types you want.  The important
  312.    things here are the procedure (which are the <acronym>C</acronym>
  313.    functions defined above) and the restriction and join selectivity
  314.    functions.  You should just use the ones used below--note that there
  315.    are different such functions for the less-than, equal, and greater-than
  316.    cases.  These must be supplied, or the access method will crash when it
  317.    tries to use the operator.  You should copy the names for restrict and
  318.    join, but use the procedure names you defined in the last step.
  319.    <programlisting>
  320. CREATE OPERATOR = (
  321.      leftarg = complex_abs, rightarg = complex_abs,
  322.      procedure = complex_abs_eq,
  323.      restrict = eqsel, join = eqjoinsel
  324.          )
  325.    </programlisting>
  326.   </para>
  327.   <para>
  328.    Notice that five operators corresponding to less,  less equal, equal,
  329.    greater, and greater equal are defined.
  330.   </para>
  331.   <para>
  332.    We're just about finished. the last thing we need to do is to update
  333.    the <filename>pg_amop</filename> relation.  To do this, we need the
  334.    following attributes:
  335.    <table tocentry="1">
  336.     <title><filename>pg_amproc</filename> Schema</title>
  337.     <titleabbrev><filename>pg_amproc</filename></titleabbrev>
  338.     <tgroup cols="2">
  339.      <thead>
  340.       <row>
  341.        <entry>Attribute</entry>
  342.        <entry>Description</entry>
  343.       </row>
  344.      </thead>
  345.      <tbody>
  346.       <row>
  347.        <entry>amopid</entry>
  348.        <entry>the <filename>oid</filename> of the <filename>pg_am</filename> instance
  349. for  B-tree (== 403, see above)</entry>
  350.       </row>
  351.       <row>
  352.        <entry>amopclaid</entry>
  353.        <entry>the <filename>oid</filename> of the
  354. <filename>pg_opclass</filename>  instance for <filename>complex_abs_ops</filename>
  355. (== whatever you got instead  of <filename>17314</filename>, see above)</entry>
  356.       </row>
  357.       <row>
  358.        <entry>amopopr</entry>
  359.        <entry>the <filename>oid</filename>s of the  operators  for the opclass
  360. (which we'll get in just a minute)</entry>
  361.       </row>
  362.       <row>
  363.        <entry>amopselect, amopnpages</entry>
  364.        <entry>cost functions</entry>
  365.       </row>
  366.      </tbody>
  367.     </tgroup>
  368.    </table>
  369.    The cost functions are used by the query optimizer to decide whether or
  370.    not to use a given index in a scan.  Fortunately, these already exist.
  371.    The two functions we'll use are <filename>btreesel</filename>, which
  372.    estimates the selectivity of the <acronym>B-tree</acronym>, and
  373.    <filename>btreenpage</filename>, which estimates the number of pages a
  374.    search will touch in the tree.
  375.   </para>
  376.   <para>
  377.    So we need the <filename>oid</filename>s of the operators we just
  378.    defined.  We'll look up the names of all the operators that take
  379.    two <filename>complex</filename>es, and pick ours out:
  380.    
  381.    <programlisting>
  382.     SELECT o.oid AS opoid, o.oprname
  383.      INTO TABLE complex_ops_tmp
  384.      FROM pg_operator o, pg_type t
  385.      WHERE o.oprleft = t.oid and o.oprright = t.oid
  386.       and t.typname = 'complex_abs';
  387.          +------+---------+
  388.          |oid   | oprname |
  389.          +------+---------+
  390.          |17321 | &lt;       |
  391.          +------+---------+
  392.          |17322 | &lt;=      |
  393.          +------+---------+
  394.          |17323 |  =      |
  395.          +------+---------+
  396.          |17324 | &gt;=      |
  397.          +------+---------+
  398.          |17325 | &gt;       |
  399.          +------+---------+
  400.    </programlisting>
  401.    (Again, some of your <filename>oid</filename> numbers will almost
  402.    certainly be different.)  The operators we are interested in are those
  403.    with <filename>oid</filename>s 17321 through 17325.  The values you
  404.    get will probably be different, and you should substitute them for the
  405.    values below.  We will do this with a select statement.
  406.   </para>
  407.   <para>
  408.    Now we're ready to update <filename>pg_amop</filename> with our new
  409.    operator class.  The most important thing in this entire discussion
  410.    is that the operators are ordered, from less equal through greater
  411.    equal, in <filename>pg_amop</filename>.  We add the instances we need:
  412.    <programlisting>
  413.     INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy,
  414.                 amopselect, amopnpages) 
  415.         SELECT am.oid, opcl.oid, c.opoid, 1,
  416.                 'btreesel'::regproc, 'btreenpage'::regproc
  417.         FROM pg_am am, pg_opclass opcl, complex_abs_ops_tmp c
  418.         WHERE amname = 'btree' AND
  419.             opcname = 'complex_abs_ops' AND
  420.             c.oprname = '<';
  421.    </programlisting>
  422.    Now do this for the other operators substituting for the "1" in the
  423.    third line above and the "<" in the last line.  Note the order:
  424.    "less than" is 1, "less than or equal" is 2, "equal" is 3, "greater
  425.    than or equal" is 4, and "greater than" is 5.
  426.   </para>
  427.   <para>
  428.    The next step is registration of the "support routine" previously
  429.    described in our discussion of <filename>pg_am</filename>.  The
  430.    <filename>oid</filename> of this support routine is stored in the
  431.    <filename>pg_amproc</filename> class, keyed by the access method
  432.    <filename>oid</filename> and the operator class <filename>oid</filename>.
  433.    First, we need to register the function in
  434.    <productname>Postgres</productname> (recall that we put the
  435.    <acronym>C</acronym> code that implements this routine in the bottom of
  436.    the file in which we implemented the operator routines):
  437.    <programlisting>
  438.     CREATE FUNCTION complex_abs_cmp(complex, complex)
  439.      RETURNS int4
  440.      AS 'PGROOT/tutorial/obj/complex.so'
  441.      LANGUAGE 'c';
  442.     SELECT oid, proname FROM pg_proc
  443.      WHERE proname = 'complex_abs_cmp';
  444.          +------+-----------------+
  445.          |oid   | proname         |
  446.          +------+-----------------+
  447.          |17328 | complex_abs_cmp |
  448.          +------+-----------------+
  449.    </programlisting>
  450.    (Again, your <filename>oid</filename> number will probably be different
  451.    and you should substitute the value you see for the value below.)
  452.    We can add the new instance as follows:
  453.    <programlisting>
  454.     INSERT INTO pg_amproc (amid, amopclaid, amproc, amprocnum)
  455.         SELECT a.oid, b.oid, c.oid, 1
  456.             FROM pg_am a, pg_opclass b, pg_proc c
  457.             WHERE a.amname = 'btree' AND
  458.                 b.opcname = 'complex_abs_ops' AND
  459.                 c.proname = 'complex_abs_cmp';
  460.    </programlisting>
  461.   </para>
  462.   <para>
  463.    Now we need to add a hashing strategy to allow the type to be indexed.
  464.    We do this by using another type in pg_am but we reuse the sames ops.
  465.    <programlisting>
  466.     INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy,
  467.                 amopselect, amopnpages)
  468.         SELECT am.oid, opcl.oid, c.opoid, 1,
  469.                 'hashsel'::regproc, 'hashnpage'::regproc
  470.         FROM pg_am am, pg_opclass opcl, complex_abs_ops_tmp c
  471.         WHERE amname = 'hash' AND
  472.             opcname = 'complex_abs_ops' AND
  473.             c.oprname = '=';
  474.    </programlisting>
  475.   </para>
  476.   <para>
  477.    In order to use this index in a where clause, we need to modify the
  478.    <filename>pg_operator</filename> class as follows.
  479.    <programlisting>
  480.     UPDATE pg_operator
  481.         SET oprrest = 'eqsel'::regproc, oprjoin = 'eqjoinsel'
  482.         WHERE oprname = '=' AND
  483.             oprleft = oprright AND
  484.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
  485.     
  486.     UPDATE pg_operator
  487.         SET oprrest = 'neqsel'::regproc, oprjoin = 'neqjoinsel'
  488.         WHERE oprname = '<filename>' AND
  489.             oprleft = oprright AND
  490.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
  491.     
  492.     UPDATE pg_operator
  493.         SET oprrest = 'neqsel'::regproc, oprjoin = 'neqjoinsel'
  494.         WHERE oprname = '<filename>' AND
  495.             oprleft = oprright AND
  496.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
  497.     
  498.     UPDATE pg_operator
  499.         SET oprrest = 'intltsel'::regproc, oprjoin = 'intltjoinsel'
  500.         WHERE oprname = '<' AND 
  501.             oprleft = oprright AND
  502.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
  503.     
  504.     UPDATE pg_operator
  505.         SET oprrest = 'intltsel'::regproc, oprjoin = 'intltjoinsel'
  506.         WHERE oprname = '<=' AND
  507.             oprleft = oprright AND
  508.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
  509.     
  510.     UPDATE pg_operator
  511.         SET oprrest = 'intgtsel'::regproc, oprjoin = 'intgtjoinsel'
  512.         WHERE oprname = '>' AND
  513.             oprleft = oprright AND
  514.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');
  515.     
  516.     UPDATE pg_operator
  517.         SET oprrest = 'intgtsel'::regproc, oprjoin = 'intgtjoinsel'
  518.         WHERE oprname = '>=' AND
  519.             oprleft = oprright AND
  520.             oprleft = (SELECT oid FROM pg_type WHERE typname = 'complex_abs');</filename></filename>
  521.    </programlisting> 
  522.   </para>
  523.   <para>
  524.    And last (Finally!) we register a description of this type.
  525.    <programlisting>
  526.     INSERT INTO pg_description (objoid, description) 
  527.     SELECT oid, 'Two part G/L account'
  528.     FROM pg_type WHERE typname = 'complex_abs';
  529.    </programlisting> 
  530.   </para>
  531.  </chapter>
  532. <!-- Keep this comment at the end of the file
  533. Local variables:
  534. mode: sgml
  535. sgml-omittag:nil
  536. sgml-shorttag:t
  537. sgml-minimize-attributes:nil
  538. sgml-always-quote-attributes:t
  539. sgml-indent-step:1
  540. sgml-indent-data:t
  541. sgml-parent-document:nil
  542. sgml-default-dtd-file:"./reference.ced"
  543. sgml-exposed-tags:nil
  544. sgml-local-catalogs:"/usr/lib/sgml/catalog"
  545. sgml-local-ecat-files:nil
  546. End:
  547. -->