Hello hackers,

For a project of ours we need GTIN14 data type support. The isn extension already supports EAN13, and a '0' prefixed EAN13 is a valid GTIN14. The leftmost "new" 14th digit is a packaging level indicator which we need (= using EAN13 and faking a leading 0 in output doesn't cut it).

Looking at the code I saw every format that isn-extension supports is stored as an EAN13. Theoretically that can be changed to be GTIN14, but that would mean quite a lot of rewrite I feared, so I chose to code only GTIN14 I/O separetely to not interfere with any existing conversion magic. This yields an easier to understand patch and doesn't touch existing functionality. However it introduces redundancy to a certain extent.

Find my patch attached. Please let me know if there are things that need changes, I'll do my best to get GTIN support into postgresql.

thanks in advance
 mike
diff --git a/expected/isn.out b/expected/isn.out
index 18fe37a..b313641 100644
--- a/expected/isn.out
+++ b/expected/isn.out
@@ -18,6 +18,8 @@ INFO:  operator family "isn_ops" of access method btree is 
missing cross-type op
 INFO:  operator family "isn_ops" of access method btree is missing cross-type 
operator(s)
 INFO:  operator family "isn_ops" of access method btree is missing cross-type 
operator(s)
 INFO:  operator family "isn_ops" of access method btree is missing cross-type 
operator(s)
+INFO:  operator family "isn_ops" of access method btree is missing cross-type 
operator(s)
+INFO:  operator family "isn_ops" of access method hash is missing cross-type 
operator(s)
 INFO:  operator family "isn_ops" of access method hash is missing cross-type 
operator(s)
 INFO:  operator family "isn_ops" of access method hash is missing cross-type 
operator(s)
 INFO:  operator family "isn_ops" of access method hash is missing cross-type 
operator(s)
@@ -29,6 +31,7 @@ INFO:  operator family "isn_ops" of access method hash is 
missing cross-type ope
  amname |  opcname   
 --------+------------
  btree  | ean13_ops
+ btree  | gtin14_ops
  btree  | isbn13_ops
  btree  | isbn_ops
  btree  | ismn13_ops
@@ -37,6 +40,7 @@ INFO:  operator family "isn_ops" of access method hash is 
missing cross-type ope
  btree  | issn_ops
  btree  | upc_ops
  hash   | ean13_ops
+ hash   | gtin14_ops
  hash   | isbn13_ops
  hash   | isbn_ops
  hash   | ismn13_ops
@@ -44,11 +48,20 @@ INFO:  operator family "isn_ops" of access method hash is 
missing cross-type ope
  hash   | issn13_ops
  hash   | issn_ops
  hash   | upc_ops
-(16 rows)
+(18 rows)
 
 --
 -- test valid conversions
 --
+SELECT '1234567890128'::GTIN14, -- EAN is a GTIN14
+       '123456789012?'::GTIN14, -- compute check digit for me
+       '11234567890125'::GTIN14,
+       '01234567890128'::GTIN14;
+     gtin14      |     gtin14      |     gtin14      |     gtin14      
+-----------------+-----------------+-----------------+-----------------
+ 0123456789012-8 | 0123456789012-8 | 1123456789012-5 | 0123456789012-8
+(1 row)
+
 SELECT '9780123456786'::EAN13, -- old book
        '9790123456785'::EAN13, -- music
        '9791234567896'::EAN13, -- new book
@@ -249,6 +262,67 @@ SELECT 9780123456786::ISBN;
 ERROR:  cannot cast type bigint to isbn
 LINE 1: SELECT 9780123456786::ISBN;
                             ^
+SELECT '91234567890125'::GTIN14; -- invalid indicator
+ERROR:  Indicator digit out of range for GTIN14 number: "91234567890125"
+LINE 1: SELECT '91234567890125'::GTIN14;
+               ^
+SELECT '123456789012'::GTIN14; -- too short
+ERROR:  invalid input syntax for GTIN14 number: "123456789012"
+LINE 1: SELECT '123456789012'::GTIN14;
+               ^
+SELECT '1234567890127'::GTIN14; -- wrong checkdigit
+ERROR:  invalid check digit for GTIN14 number: "1234567890127", should be 8
+LINE 1: SELECT '1234567890127'::GTIN14;
+               ^
+--
+-- test validity helpers
+--
+SELECT make_valid('1234567890120!'::GTIN14); -- EAN-13
+   make_valid    
+-----------------
+ 0123456789012-8
+(1 row)
+
+SELECT make_valid('11234567890120!'::GTIN14); -- GTIN-14
+   make_valid    
+-----------------
+ 1123456789012-5
+(1 row)
+
+SELECT is_valid(make_valid('1234567890120!'::GTIN14)); -- EAN-13
+ is_valid 
+----------
+ t
+(1 row)
+
+SELECT is_valid(make_valid('11234567890120!'::GTIN14)); -- GTIN-14
+ is_valid 
+----------
+ t
+(1 row)
+
+CREATE TABLE gtin_valid (gtin GTIN14 NOT NULL);
+INSERT INTO gtin_valid VALUES
+-- all invalid because of ! marking
+       ('1234567890120!'), -- invalid EAN-13
+       ('1234567890128!'), -- valid EAN-13
+       ('11234567890120!'), -- invalid GTIN-14
+       ('11234567890125!'), -- valid GTIN-14
+-- valid
+       ('1234567890128'::GTIN14), -- valid EAN-13
+       ('11234567890125'::GTIN14); -- valid GTIN-14
+SELECT gtin, is_valid(gtin) FROM gtin_valid;
+       gtin       | is_valid 
+------------------+----------
+ 0123456789012-8! | f
+ 0123456789012-8! | f
+ 1123456789012-5! | f
+ 1123456789012-5! | f
+ 0123456789012-8  | t
+ 1123456789012-5  | t
+(6 rows)
+
+DROP TABLE gtin_valid;
 --
 -- test some comparisons, must yield true
 --
diff --git a/isn--1.1.sql b/isn--1.1.sql
index 5206961..fd3f157 100644
--- a/isn--1.1.sql
+++ b/isn--1.1.sql
@@ -15,6 +15,26 @@
 --     Input and output functions and data types:
 --
 ---------------------------------------------------
+CREATE FUNCTION gtin14_in(cstring)
+       RETURNS gtin14
+       AS 'MODULE_PATHNAME'
+       LANGUAGE C
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE FUNCTION gtin14_out(gtin14)
+       RETURNS cstring
+       AS 'MODULE_PATHNAME'
+       LANGUAGE C
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE TYPE gtin14 (
+       INPUT = gtin14_in,
+       OUTPUT = gtin14_out,
+       LIKE = pg_catalog.int8
+);
+COMMENT ON TYPE gtin14
+       IS 'Global Trade Item Number (GTIN-14)';
+
 CREATE FUNCTION ean13_in(cstring)
        RETURNS ean13
        AS 'MODULE_PATHNAME'
@@ -181,6 +201,44 @@ COMMENT ON TYPE upc
 -- Operator functions:
 --
 ---------------------------------------------------
+-- GTIN-14:
+CREATE FUNCTION isnlt(gtin14, gtin14)
+       RETURNS boolean
+       AS 'int8lt'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE FUNCTION isnle(gtin14, gtin14)
+       RETURNS boolean
+       AS 'int8le'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE FUNCTION isneq(gtin14, gtin14)
+       RETURNS boolean
+       AS 'int8eq'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE FUNCTION isnge(gtin14, gtin14)
+       RETURNS boolean
+       AS 'int8ge'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE FUNCTION isngt(gtin14, gtin14)
+       RETURNS boolean
+       AS 'int8gt'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+CREATE FUNCTION isnne(gtin14, gtin14)
+       RETURNS boolean
+       AS 'int8ne'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+
 -- EAN13:
 CREATE FUNCTION isnlt(ean13, ean13)
        RETURNS boolean
@@ -1236,6 +1294,61 @@ CREATE FUNCTION isnne(upc, ean13)
 -- Now the operators:
 --
 
+--
+-- GTIN-14 operators:
+--
+---------------------------------------------------
+CREATE OPERATOR < (
+       PROCEDURE = isnlt,
+       LEFTARG = gtin14,
+       RIGHTARG = gtin14,
+       COMMUTATOR = >,
+       NEGATOR = >=,
+       RESTRICT = scalarltsel,
+       JOIN = scalarltjoinsel);
+CREATE OPERATOR <= (
+       PROCEDURE = isnle,
+       LEFTARG = gtin14,
+       RIGHTARG = gtin14,
+       COMMUTATOR = >=,
+       NEGATOR = >,
+       RESTRICT = scalarltsel,
+       JOIN = scalarltjoinsel);
+CREATE OPERATOR = (
+       PROCEDURE = isneq,
+       LEFTARG = gtin14,
+       RIGHTARG = gtin14,
+       COMMUTATOR = =,
+       NEGATOR = <>,
+       RESTRICT = eqsel,
+       JOIN = eqjoinsel,
+       MERGES,
+       HASHES);
+CREATE OPERATOR >= (
+       PROCEDURE = isnge,
+       LEFTARG = gtin14,
+       RIGHTARG = gtin14,
+       COMMUTATOR = <=,
+       NEGATOR = <,
+       RESTRICT = scalargtsel,
+       JOIN = scalargtjoinsel );
+CREATE OPERATOR > (
+       PROCEDURE = isngt,
+       LEFTARG = gtin14,
+       RIGHTARG = gtin14,
+       COMMUTATOR = <,
+       NEGATOR = <=,
+       RESTRICT = scalargtsel,
+       JOIN = scalargtjoinsel );
+CREATE OPERATOR <> (
+       PROCEDURE = isnne,
+       LEFTARG = gtin14,
+       RIGHTARG = gtin14,
+       COMMUTATOR = <>,
+       NEGATOR = =,
+       RESTRICT = neqsel,
+       JOIN = neqjoinsel);
+
 --
 -- EAN13 operators:
 --
@@ -2708,6 +2821,34 @@ CREATE OPERATOR FAMILY isn_ops USING hash;
 -- Operator classes:
 --
 ---------------------------------------------------
+-- GTIN-14:
+CREATE FUNCTION btgtin14cmp(gtin14, gtin14)
+       RETURNS int4
+       AS 'btint8cmp'
+       LANGUAGE 'internal'
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
+
+CREATE OPERATOR CLASS gtin14_ops DEFAULT
+       FOR TYPE gtin14 USING btree FAMILY isn_ops AS
+       OPERATOR 1  <,
+       OPERATOR 2  <=,
+       OPERATOR 3  =,
+       OPERATOR 4  >=,
+       OPERATOR 5  >,
+       FUNCTION 1  btgtin14cmp(gtin14, gtin14);
+
+CREATE FUNCTION hashgtin14(gtin14)
+       RETURNS int4
+       AS 'hashint8'
+       LANGUAGE 'internal' IMMUTABLE STRICT
+       PARALLEL SAFE;
+
+CREATE OPERATOR CLASS gtin14_ops DEFAULT
+       FOR TYPE gtin14 USING hash FAMILY isn_ops AS
+       OPERATOR 1  =,
+       FUNCTION 1  hashgtin14(gtin14);
+
 -- EAN13:
 CREATE FUNCTION btean13cmp(ean13, ean13)
        RETURNS int4
@@ -3314,6 +3455,12 @@ CREATE CAST (issn13 AS issn) WITHOUT FUNCTION AS 
ASSIGNMENT;
 --
 -- Validation stuff for lose types:
 --
+CREATE FUNCTION make_valid(gtin14)
+       RETURNS gtin14
+       AS 'MODULE_PATHNAME'
+       LANGUAGE C
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
 CREATE FUNCTION make_valid(ean13)
        RETURNS ean13
        AS 'MODULE_PATHNAME'
@@ -3363,6 +3510,12 @@ CREATE FUNCTION make_valid(upc)
        IMMUTABLE STRICT
        PARALLEL SAFE;
 
+CREATE FUNCTION is_valid(gtin14)
+       RETURNS boolean
+       AS 'MODULE_PATHNAME'
+       LANGUAGE C
+       IMMUTABLE STRICT
+       PARALLEL SAFE;
 CREATE FUNCTION is_valid(ean13)
        RETURNS boolean
        AS 'MODULE_PATHNAME'
diff --git a/isn.c b/isn.c
index 0c2cac7..d7baa78 100644
--- a/isn.c
+++ b/isn.c
@@ -36,10 +36,10 @@ PG_MODULE_MAGIC;
 
 enum isn_type
 {
-       INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC
+       INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC, GTIN14
 };
 
-static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", 
"EAN13", "ISBN", "ISMN", "ISSN", "UPC"};
+static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", 
"EAN13", "ISBN", "ISMN", "ISSN", "UPC", "GTIN14"};
 
 static bool g_weak = false;
 
@@ -325,7 +325,10 @@ checkdig(char *num, unsigned size)
                }
                num++;
        }
-       check = (check + 3 * check3) % 10;
+       if (pos % 2 == 0)  /* for even length strings */
+               check = (check + 3 * check3) % 10;
+       else
+               check = (check3 + 3 * check) % 10;
        if (check != 0)
                check = 10 - check;
        return check;
@@ -500,11 +503,9 @@ ean2UPC(char *isn)
 }
 
 /*
- * ean2* --- Converts a string of digits into an ean13 number.
- *                       Assumes the input string is a string with only digits
- *                       on it, and that it's within the range of ean13.
+ * str2ean --- Converts a string of digits into binary storage format.
  *
- * Returns the ean13 value of the string.
+ * Returns the ean13 with valid-flag set to true.
  */
 static ean13
 str2ean(const char *num)
@@ -520,6 +521,71 @@ str2ean(const char *num)
        return (ean << 1);                      /* also give room to a flag */
 }
 
+/*
+ * gtin14_2string --- Try to convert GTINumber to a hyphenated string.
+ *                               Assumes there's enough space in result to hold
+ *                               the string (maximum MAXEAN13LEN+1 bytes)
+ *                               This doesn't verify for a valid check digit.
+ *
+ * If errorOK is false, ereport a useful error message if the string is bad.
+ * If errorOK is true, just return "false" for bad input.
+ */
+static bool
+gtin14_2string(ean13 ean, bool errorOK, char *result)
+{
+       enum isn_type type = INVALID;
+
+       char       *aux;
+       unsigned        digval;
+       unsigned        search;
+       char            valid = '\0';   /* was the number initially written 
with a
+                                                                * valid check 
digit? */
+
+       if ((ean & 1) != 0)
+               valid = '!';
+       ean >>= 1;
+       /* verify it's in the EAN13 range */
+       if (ean > UINT64CONST(99999999999999))
+               goto eantoobig;
+
+       /* convert the number */
+       search = 0;
+       aux = result + MAXEAN13LEN;
+       *aux = '\0';                            /* terminate string; aux points 
to last digit */
+       *--aux = valid;                         /* append '!' for numbers with 
invalid but
+                                                                * corrected 
check digit */
+       do
+       {
+               digval = (unsigned) (ean % 10); /* get the decimal value */
+               ean /= 10;                              /* get next digit */
+               *--aux = (char) (digval + '0'); /* convert to ascii and store */
+               if (search == 0)
+                       *--aux = '-';           /* the check digit is always 
there */
+       } while (ean && search++ < 14);
+       while (search++ < 14)
+               *--aux = '0';                   /* fill the remaining GTIN14 
with '0' */
+
+       search = hyphenate(result, result + 2, NULL, NULL);
+
+       return true;
+
+eantoobig:
+       if (!errorOK)
+       {
+               char            eanbuf[64];
+
+               /*
+                * Format the number separately to keep the machine-dependent 
format
+                * code out of the translatable message text
+                */
+               snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean);
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("value \"%s\" is out of range for %s 
type",
+                                               eanbuf, isn_names[type])));
+       }
+       return false;
+}
 /*
  * ean2string --- Try to convert an ean13 number to a hyphenated string.
  *                               Assumes there's enough space in result to hold
@@ -673,6 +739,162 @@ eantoobig:
        return false;
 }
 
+/*
+ * string2gtin14 --- try to parse a string into an gtin14.
+ *
+ * If errorOK is false, ereport a useful error message if the string is bad.
+ * If errorOK is true, just return "false" for bad input.
+ *
+ * if the input string ends with '!' it will always be treated as invalid
+ * (even if the check digit is valid)
+ */
+static bool
+string2gtin14(const char *str, bool errorOK, ean13 *result,
+                  enum isn_type accept)
+{
+       bool            digit,
+                               last;
+       char            buf[17] = "                ";
+       char       *aux1 = buf + 2; /* leave space for the first part, in case
+                                                                * it's needed 
*/
+       const char *aux2 = str;
+       enum isn_type type = INVALID;
+       unsigned        check = 0,
+                               rcheck = (unsigned) -1;
+       unsigned        length = 0;
+       bool            magic = false,
+                               valid = true;
+
+       /* recognize and validate the number: */
+       while (*aux2 && length <= 14)
+       {
+               last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0'); /* is the 
last character */
+               digit = (isdigit((unsigned char) *aux2) != 0);  /* is current 
character
+                                                                               
                                 * a digit? */
+               if (*aux2 == '?' && last)       /* automagically calculate 
check digit if
+                                                                        * it's 
'?' */
+                       magic = digit = true;
+               if (*aux2 == '!' && *(aux2 + 1) == '\0')
+               {
+                       /* the invalid check digit suffix was found, set it */
+                       if (!magic)
+                               valid = false;
+                       magic = true;
+               }
+               else if (!digit)
+               {
+                       goto eaninvalid;
+               }
+               else
+               {
+                       *aux1++ = *aux2;
+                       if (++length > 14)
+                               goto eantoobig;
+               }
+               aux2++;
+       }
+       *aux1 = '\0';                           /* terminate the string */
+
+       /* find the current check digit value */
+       if (length == 13)
+       {
+               /* is likely EAN13, prefix with 0 to make valid GTIN14 */
+               type = EAN13;
+               buf[1] = '0';  /* prefix 0 */
+               check = buf[14] - '0';
+       }
+       else if (length == 14)
+       {
+               /* is likely GTIN14 */
+               type = GTIN14;
+               check = buf[15] - '0';
+
+               if (buf[2] - '0' == 9) /* valid range of indicator digit is 0-8 
*/
+                       goto indicatorinvalid;
+       }
+       else
+               goto eaninvalid;
+
+       if (type == INVALID)
+               goto eaninvalid;
+
+       /* compute check digit: */
+       for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++);
+       rcheck = checkdig(aux1, 14);
+       /* validate check digit and convert to gtin14: */
+       switch (type)
+       {
+               case GTIN14:
+               case EAN13:
+                       valid = (valid && (rcheck == check || magic));
+                       break;
+               default:
+                       break;
+       }
+       /* always fix the check digit: */
+       aux1[13] = rcheck + '0';
+       aux1[14] = '\0';
+
+       if (!valid && !magic)
+               goto eanbadcheck;
+
+       *result = str2ean(aux1);
+       *result |= valid ? 0 : 1;
+       return true;
+
+eanbadcheck:
+       if (g_weak)
+       {                                                       /* weak input 
mode is activated: */
+               /* set the "invalid-check-digit-on-input" flag */
+               *result = str2ean(aux1);
+               *result |= 1;
+               return true;
+       }
+
+       if (!errorOK)
+       {
+               if (rcheck == (unsigned) -1)
+               {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("invalid %s number: \"%s\"",
+                                                       isn_names[accept], 
str)));
+               }
+               else
+               {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                        errmsg("invalid check digit for %s 
number: \"%s\", should be %c",
+                                                       isn_names[accept], str, 
(rcheck == 10) ? ('X') : (rcheck + '0'))));
+               }
+       }
+       return false;
+
+eaninvalid:
+       if (!errorOK)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                errmsg("invalid input syntax for %s number: 
\"%s\"",
+                                               isn_names[accept], str)));
+       return false;
+
+indicatorinvalid:
+       if (!errorOK)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                errmsg("Indicator digit out of range for %s 
number: \"%s\"",
+                                               isn_names[accept], str)));
+       return false;
+
+eantoobig:
+       if (!errorOK)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("value \"%s\" is out of range for %s 
type",
+                                               str, isn_names[accept])));
+       return false;
+}
+
 /*
  * string2ean --- try to parse a string into an ean13.
  *
@@ -990,6 +1212,35 @@ ean13_in(PG_FUNCTION_ARGS)
        PG_RETURN_EAN13(result);
 }
 
+/* gtin14_out
+ */
+PG_FUNCTION_INFO_V1(gtin14_out);
+Datum
+gtin14_out(PG_FUNCTION_ARGS)
+{
+       ean13           val = PG_GETARG_EAN13(0);
+       char       *result;
+       char            buf[MAXEAN13LEN + 1];
+
+       (void) gtin14_2string(val, false, buf);
+
+       result = pstrdup(buf);
+       PG_RETURN_CSTRING(result);
+}
+
+/* gtin14_in
+ */
+PG_FUNCTION_INFO_V1(gtin14_in);
+Datum
+gtin14_in(PG_FUNCTION_ARGS)
+{
+       const char *str = PG_GETARG_CSTRING(0);
+       ean13           result;
+
+       (void) string2gtin14(str, false, &result, GTIN14);
+       PG_RETURN_EAN13(result);
+}
+
 /* isbn_in
  */
 PG_FUNCTION_INFO_V1(isbn_in);
diff --git a/sql/isn.sql b/sql/isn.sql
index 71577d5..ede461f 100644
--- a/sql/isn.sql
+++ b/sql/isn.sql
@@ -16,6 +16,11 @@ WHERE NOT amvalidate(oid);
 --
 -- test valid conversions
 --
+SELECT '1234567890128'::GTIN14, -- EAN is a GTIN14
+       '123456789012?'::GTIN14, -- compute check digit for me
+       '11234567890125'::GTIN14,
+       '01234567890128'::GTIN14;
+
 SELECT '9780123456786'::EAN13, -- old book
        '9790123456785'::EAN13, -- music
        '9791234567896'::EAN13, -- new book
@@ -100,6 +105,30 @@ SELECT 'postgresql...'::ISBN;
 SELECT 9780123456786::EAN13;
 SELECT 9780123456786::ISBN;
 
+SELECT '91234567890125'::GTIN14; -- invalid indicator
+SELECT '123456789012'::GTIN14; -- too short
+SELECT '1234567890127'::GTIN14; -- wrong checkdigit
+
+--
+-- test validity helpers
+--
+SELECT make_valid('1234567890120!'::GTIN14); -- EAN-13
+SELECT make_valid('11234567890120!'::GTIN14); -- GTIN-14
+SELECT is_valid(make_valid('1234567890120!'::GTIN14)); -- EAN-13
+SELECT is_valid(make_valid('11234567890120!'::GTIN14)); -- GTIN-14
+
+CREATE TABLE gtin_valid (gtin GTIN14 NOT NULL);
+INSERT INTO gtin_valid VALUES
+-- all invalid because of ! marking
+       ('1234567890120!'), -- invalid EAN-13
+       ('1234567890128!'), -- valid EAN-13
+       ('11234567890120!'), -- invalid GTIN-14
+       ('11234567890125!'), -- valid GTIN-14
+-- valid
+       ('1234567890128'::GTIN14), -- valid EAN-13
+       ('11234567890125'::GTIN14); -- valid GTIN-14
+SELECT gtin, is_valid(gtin) FROM gtin_valid;
+DROP TABLE gtin_valid;
 --
 -- test some comparisons, must yield true
 --

Reply via email to