Search Postgresql Archives

Custom type, operators and operator class not sorting/indexing correctly

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Dear all,

I've created a new domain (debversion) derived from TEXT, which
includes its own operators (< <= = >= > and <>), and also its
own operator class for BTREE indices.

The operators function correctly when I test them by themselves,
e.g. SELECT x < y;
However, if I create a table with a column of this type, ORDER BY
does not result in correct ordering.  I have to explicitly add
'USING <' to the query, and even this fails to work if I haven't
defined the operator class:

# SELECT * FROM testv ORDER BY version ASC;
     version
------------------
 1.0.3-3
 3.0.7+1-1
 3.0.7+1-2
 3.0.7+1-2~lenny2
(4 rows)

# SELECT * FROM testv ORDER BY version USING <;
     version
------------------
 1.0.3-3
 3.0.7+1-1
 3.0.7+1-2~lenny2
 3.0.7+1-2
(4 rows)

The latter shows the correct ordering.  The former appears to be
using the lexical ordering of the TEXT type.  Adding an index
does not affect the ordering, even if I explictly make it use my
operator class (it's also set as the default).

The SQL code to create the type and demonstrate the problem follows
at the end of this mail.  It requires the PL/Perl and PL/pgSQL
languages to be available.  It shows example queries to demonstrate
the ordering issue above.

I thought that I had correctly defined the type, functions, operators
and operator class in order for everything to function correctly, but
I must be missing some final piece of the puzzle or some PostgreSQL
subtlety I'm not aware of (this is my first attempt at defining
operators, and I am also a newcomer to using procedural languages).

Could anyone suggest what I've done wrong here?


Many thanks,
Roger Leigh

-- 
  .''`.  Roger Leigh
 : :' :  Debian GNU/Linux             http://people.debian.org/~rleigh/
 `. `'   Printing on GNU/Linux?       http://gutenprint.sourceforge.net/
   `-    GPG Public Key: 0x25BFB848   Please GPG sign your mail.
--- WannaBuild Database Schema for PostgreSQL                        -*- sql -*-
--- Debian version type and operators
---
--- Code derived from Dpkg::Version:
--- Copyright © Colin Watson <cjwatson@xxxxxxxxxx>
--- Copyright © Ian Jackson <iwj@xxxxxxxxxx>
--- Copyright © 2007 by Don Armstrong <don@xxxxxxxxxxxxxxxx>
---
--- PostgreSQL SQL, PL/pgSQL and PL/Perl:
--- Copyright © 2008 Roger Leigh <rleigh@xxxxxxxxxx>
---
--- This program is free software: you can redistribute it and/or modify
--- it under the terms of the GNU General Public License as published by
--- the Free Software Foundation, either version 2 of the License, or
--- (at your option) any later version.
---
--- This program is distributed in the hope that it will be useful, but
--- WITHOUT ANY WARRANTY; without even the implied warranty of
--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
--- General Public License for more details.
---
--- You should have received a copy of the GNU General Public License
--- along with this program.  If not, see
--- <http://www.gnu.org/licenses/>.

SET SESSION plperl.use_strict TO 't';

CREATE DOMAIN debversion AS TEXT;
COMMENT ON DOMAIN debversion IS 'Debian package version number';

ALTER DOMAIN debversion
  ADD CONSTRAINT debversion_syntax
    CHECK (VALUE !~ '[^-+:.0-9a-zA-Z~]');

-- From Dpkg::Version::parseversion
CREATE OR REPLACE FUNCTION debversion_split (debversion)
  RETURNS text[] AS $$
    my $ver = shift;
    my %verhash;
    if ($ver =~ /:/)
    {
        $ver =~ /^(\d+):(.+)/ or die "bad version number '$ver'";
        $verhash{epoch} = $1;
        $ver = $2;
    }
    else
    {
        $verhash{epoch} = 0;
    }
    if ($ver =~ /(.+)-(.*)$/)
    {
        $verhash{version} = $1;
        $verhash{revision} = $2;
    }
    else
    {
        $verhash{version} = $ver;
        $verhash{revision} = 0;
    }

    return [$verhash{'epoch'}, $verhash{'version'}, $verhash{'revision'}];
$$
  LANGUAGE plperl
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_split (debversion)
  IS 'Split debian version into epoch, upstream version and revision';

CREATE OR REPLACE FUNCTION debversion_epoch (version debversion)
  RETURNS text AS $$
DECLARE
  split text[];
BEGIN
  split := debversion_split(version);
  RETURN split[1];
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_epoch (debversion)
  IS 'Get debian version epoch';

CREATE OR REPLACE FUNCTION debversion_version (version debversion)
  RETURNS text AS $$
DECLARE
  split text[];
BEGIN
  split := debversion_split(version);
  RETURN split[2];
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_version (debversion)
  IS 'Get debian version upstream version';

CREATE OR REPLACE FUNCTION debversion_revision (version debversion)
  RETURNS text AS $$
DECLARE
  split text[];
BEGIN
  split := debversion_split(version);
  RETURN split[3];
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_revision (debversion)
  IS 'Get debian version revision';

-- From Dpkg::Version::parseversion
CREATE OR REPLACE FUNCTION debversion_compare_single (version1 text,
       	  	  	   			      version2 text)
  RETURNS integer AS $$
     sub order{
	  my ($x) = @_;
	  ##define order(x) ((x) == '~' ? -1 \
	  #           : cisdigit((x)) ? 0 \
	  #           : !(x) ? 0 \
	  #           : cisalpha((x)) ? (x) \
	  #           : (x) + 256)
	  # This comparison is out of dpkg's order to avoid
	  # comparing things to undef and triggering warnings.
	  if (not defined $x or not length $x) {
	       return 0;
	  }
	  elsif ($x eq '~') {
	       return -1;
	  }
	  elsif ($x =~ /^\d$/) {
	       return 0;
	  }
	  elsif ($x =~ /^[A-Z]$/i) {
	       return ord($x);
	  }
	  else {
	       return ord($x) + 256;
	  }
     }

     sub next_elem(\@){
	  my $a = shift;
	  return @{$a} ? shift @{$a} : undef;
     }
     my ($val, $ref) = @_;
     $val = "" if not defined $val;
     $ref = "" if not defined $ref;
     my @val = split //,$val;
     my @ref = split //,$ref;
     my $vc = next_elem @val;
     my $rc = next_elem @ref;
     while (defined $vc or defined $rc) {
	  my $first_diff = 0;
	  while ((defined $vc and $vc !~ /^\d$/) or
		 (defined $rc and $rc !~ /^\d$/)) {
	       my $vo = order($vc); my $ro = order($rc);
	       # Unlike dpkg's verrevcmp, we only return 1 or -1 here.
	       return (($vo - $ro > 0) ? 1 : -1) if $vo != $ro;
	       $vc = next_elem @val; $rc = next_elem @ref;
	  }
	  while (defined $vc and $vc eq '0') {
	       $vc = next_elem @val;
	  }
	  while (defined $rc and $rc eq '0') {
	       $rc = next_elem @ref;
	  }
	  while (defined $vc and $vc =~ /^\d$/ and
		 defined $rc and $rc =~ /^\d$/) {
	       $first_diff = ord($vc) - ord($rc) if !$first_diff;
	       $vc = next_elem @val; $rc = next_elem @ref;
	  }
	  return 1 if defined $vc and $vc =~ /^\d$/;
	  return -1 if defined $rc and $rc =~ /^\d$/;
	  return (($first_diff  > 0) ? 1 : -1) if $first_diff;
     }
     return 0;
$$
  LANGUAGE plperl
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_compare_single (text, text)
  IS 'Compare upstream or revision parts of Debian versions';

-- Logic only derived from Dpkg::Version::parseversion
CREATE OR REPLACE FUNCTION debversion_compare (version1 debversion,
       	  	  	   		       version2 debversion)
  RETURNS integer AS $$
DECLARE
  split1 text[];
  split2 text[];
  result integer;
BEGIN
  result := 0;
  split1 := debversion_split(version1);
  split2 := debversion_split(version2);

  -- RAISE NOTICE 'Version 1: %', version1;
  -- RAISE NOTICE 'Version 2: %', version2;
  -- RAISE NOTICE 'Split 1: %', split1;
  -- RAISE NOTICE 'Split 2: %', split2;

  IF split1[1] > split2[1] THEN
    result := 1;
  ELSIF split1[1] < split2[1] THEN
    result := -1;
  ELSE
    result := debversion_compare_single(split1[2], split2[2]);
    IF result = 0 THEN
      result := debversion_compare_single(split1[3], split2[3]);
    END IF;
  END IF;

  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_compare (debversion, debversion)
  IS 'Compare Debian versions';

CREATE OR REPLACE FUNCTION debversion_eq (version1 debversion,
       	  	  	   		  version2 debversion)
  RETURNS boolean AS $$
DECLARE
  comp integer;
  result boolean;
BEGIN
  comp := debversion_compare(version1, version2);
  result := comp = 0;
  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_eq (debversion, debversion)
  IS 'debversion equal';

CREATE OR REPLACE FUNCTION debversion_ne (version1 debversion,
       	  	  	   		  version2 debversion)
  RETURNS boolean AS $$
DECLARE
  comp integer;
  result boolean;
BEGIN
  comp := debversion_compare(version1, version2);
  result := comp <> 0;
  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_ne (debversion, debversion)
  IS 'debversion not equal';

CREATE OR REPLACE FUNCTION debversion_lt (version1 debversion,
       	  	  	   		  version2 debversion)
  RETURNS boolean AS $$
DECLARE
  comp integer;
  result boolean;
BEGIN
  comp := debversion_compare(version1, version2);
  result := comp < 0;
  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_lt (debversion, debversion)
  IS 'debversion less-than';

CREATE OR REPLACE FUNCTION debversion_gt (version1 debversion,
       	  	  	   		  version2 debversion) RETURNS boolean AS $$
DECLARE
  comp integer;
  result boolean;
BEGIN
  comp := debversion_compare(version1, version2);
  result := comp > 0;
  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_gt (debversion, debversion)
  IS 'debversion greater-than';

CREATE OR REPLACE FUNCTION debversion_le (version1 debversion,
       	  	  	   		  version2 debversion)
  RETURNS boolean AS $$
DECLARE
  comp integer;
  result boolean;
BEGIN
  comp := debversion_compare(version1, version2);
  result := comp <= 0;
  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_le (debversion, debversion)
  IS 'debversion less-than-or-equal';

CREATE OR REPLACE FUNCTION debversion_ge (version1 debversion,
       	  	  	   		  version2 debversion)
  RETURNS boolean AS $$
DECLARE
  comp integer;
  result boolean;
BEGIN
  comp := debversion_compare(version1, version2);
  result := comp >= 0;
  RETURN result;
END;
$$
  LANGUAGE plpgsql
  IMMUTABLE STRICT;
COMMENT ON FUNCTION debversion_ge (debversion, debversion)
  IS 'debversion greater-than-or-equal';

CREATE OPERATOR = (
  PROCEDURE = debversion_eq,
  LEFTARG = debversion,
  RIGHTARG = debversion,
  COMMUTATOR = =,
  NEGATOR = !=
);
COMMENT ON OPERATOR = (debversion, debversion)
  IS 'debversion equal';

CREATE OPERATOR != (
  PROCEDURE = debversion_eq,
  LEFTARG = debversion,
  RIGHTARG = debversion,
  COMMUTATOR = !=,
  NEGATOR = =
);
COMMENT ON OPERATOR != (debversion, debversion)
  IS 'debversion not equal';

CREATE OPERATOR < (
  PROCEDURE = debversion_lt,
  LEFTARG = debversion,
  RIGHTARG = debversion,
  COMMUTATOR = >,
  NEGATOR = >=
);
COMMENT ON OPERATOR < (debversion, debversion)
  IS 'debversion less-than';

CREATE OPERATOR > (
  PROCEDURE = debversion_gt,
  LEFTARG = debversion,
  RIGHTARG = debversion,
  COMMUTATOR = <,
  NEGATOR = >=
);
COMMENT ON OPERATOR > (debversion, debversion)
  IS 'debversion greater-than';

CREATE OPERATOR <= (
  PROCEDURE = debversion_le,
  LEFTARG = debversion,
  RIGHTARG = debversion,
  COMMUTATOR = >=,
  NEGATOR = >
);
COMMENT ON OPERATOR <= (debversion, debversion)
  IS 'debversion less-than-or-equal';

CREATE OPERATOR >= (
  PROCEDURE = debversion_ge,
  LEFTARG = debversion,
  RIGHTARG = debversion,
  COMMUTATOR = <=,
  NEGATOR = <
);
COMMENT ON OPERATOR >= (debversion, debversion)
  IS 'debversion greater-than-or-equal';

CREATE OPERATOR CLASS debversion_ops
  DEFAULT FOR TYPE debversion USING BTREE AS
    OPERATOR 1 <,
    OPERATOR 2 <=,
    OPERATOR 3 =,
    OPERATOR 4 >=,
    OPERATOR 5 >,
    FUNCTION 1 debversion_compare(debversion,debversion);

CREATE TABLE testv (version debversion);
INSERT INTO testv (version) VALUES ('3.0.7+1-2~lenny2');
INSERT INTO testv (version) VALUES ('3.0.7+1-2');
INSERT INTO testv (version) VALUES ('3.0.7+1-1');
INSERT INTO testv (version) VALUES ('1.0.3-3');

SELECT * FROM testv;

SELECT * FROM testv ORDER BY version ASC;

SELECT * FROM testv ORDER BY version DESC;

SELECT '3.0.7+1-2'::debversion < '3.0.7+1-2~lenny2'::debversion;

SELECT '3.0.7+1-2'::debversion > '3.0.7+1-2~lenny2'::debversion;

SELECT '3.0.7+1-2'::debversion = '3.0.7+1-2~lenny2'::debversion;

CREATE TABLE testt (version text);
INSERT INTO testt SELECT * FROM testv;

SELECT * FROM testt ORDER BY version ASC;

SELECT * FROM testt ORDER BY version DESC;

SELECT * FROM testt ORDER BY version USING <;

SELECT * FROM testv ORDER BY version USING <;

Attachment: signature.asc
Description: Digital signature


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Postgresql Jobs]     [Postgresql Admin]     [Postgresql Performance]     [Linux Clusters]     [PHP Home]     [PHP on Windows]     [Kernel Newbies]     [PHP Classes]     [PHP Books]     [PHP Databases]     [Postgresql & PHP]     [Yosemite]
  Powered by Linux