Package PGSQL extension with Spanish VAT check
This commit is contained in:
commit
a2069e102c
|
@ -0,0 +1,4 @@
|
|||
*.o
|
||||
*.so
|
||||
regression.*
|
||||
results/
|
|
@ -0,0 +1,12 @@
|
|||
MODULE_big = vat
|
||||
OBJS = vatin.o es.o
|
||||
|
||||
EXTENSION = vat
|
||||
DATA = vat--0.0.sql
|
||||
PGFILEDESC = "vat - data type for VAT identification numbers"
|
||||
|
||||
REGRESS = vat
|
||||
|
||||
PG_CONFIG := pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
#include "es.h"
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <postgres.h>
|
||||
#include <utils/palloc.h>
|
||||
|
||||
static char
|
||||
compute_dni_check_digit(const char *start, const char *end)
|
||||
{
|
||||
unsigned long int n = 0;
|
||||
const char *p = start;
|
||||
for(; p != end; p++) {
|
||||
n = n * 10 + *p - 48;
|
||||
}
|
||||
return "TRWAGMYFPDXBNJZSQVHLCKE"[n % 23];
|
||||
}
|
||||
|
||||
static bool
|
||||
valid_short_dni(const char *dni) {
|
||||
return dni[8] == compute_dni_check_digit(dni + 1, dni + 8);
|
||||
}
|
||||
|
||||
static bool
|
||||
valid_dni(const char *dni) {
|
||||
return dni[8] == compute_dni_check_digit(dni, dni + 8);
|
||||
}
|
||||
|
||||
static bool
|
||||
valid_nie(char *nie) {
|
||||
bool valid = false;
|
||||
nie[0] -= 'X' - 48;
|
||||
valid = nie[8] == compute_dni_check_digit(nie, nie + 8);
|
||||
nie[0] += 'X' - 48;
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool
|
||||
valid_cif(const char *cif) {
|
||||
int odds = 0;
|
||||
int evens = 0;
|
||||
int n = 0;
|
||||
int checksum;
|
||||
|
||||
for (n = 1; n < 8; n++) {
|
||||
if (n % 2 == 0) {
|
||||
evens += cif[n] - 48;
|
||||
} else {
|
||||
int d = cif[n] * 2;
|
||||
int a = d / 10;
|
||||
int b = d % 10;
|
||||
odds += a + b;
|
||||
}
|
||||
}
|
||||
checksum = 10 - (odds + evens) % 10;
|
||||
return cif[8] == checksum + 48 || cif[8] == "JABCDEFGHI"[checksum];
|
||||
}
|
||||
|
||||
char *
|
||||
check_es_vat(const char *str, size_t len) {
|
||||
const char *p = str;
|
||||
const char *end = p + len;
|
||||
char buf[10] = { 0 };
|
||||
int n = 0;
|
||||
|
||||
if (len < 9 ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (; p != end && n < 10; p++) {
|
||||
char c = *p;
|
||||
if (c == ' ' || c == '-') continue;
|
||||
if (n > 0 && n < 8) {
|
||||
if (!isdigit(c)) {
|
||||
return NULL;
|
||||
}
|
||||
buf[n] = c;
|
||||
} else {
|
||||
buf[n] = toupper(c);
|
||||
}
|
||||
n++;
|
||||
}
|
||||
if (n == 10) {
|
||||
return NULL;
|
||||
}
|
||||
buf[9] = 0;
|
||||
|
||||
switch (buf[0]) {
|
||||
case 'K':
|
||||
case 'L':
|
||||
case 'M':
|
||||
if (!valid_short_dni(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
if (!valid_dni(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'X':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
if (!valid_nie(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'G':
|
||||
case 'H':
|
||||
case 'J':
|
||||
case 'N':
|
||||
case 'P':
|
||||
case 'Q':
|
||||
case 'R':
|
||||
case 'S':
|
||||
case 'U':
|
||||
case 'V':
|
||||
case 'W':
|
||||
if (!valid_cif(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pstrdup(buf);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
#ifndef VAT_ES_H
|
||||
#define VAT_ES_H 1
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
char * check_es_vat(const char *str, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VAT_ES_H */
|
|
@ -0,0 +1,31 @@
|
|||
--
|
||||
-- Test VATIN extensions
|
||||
--
|
||||
CREATE EXTENSION vat;
|
||||
-- Test valid conversions
|
||||
SELECT 'ES40404040D'::VATIN,
|
||||
'ES40404040-D'::VATIN,
|
||||
' esx0523821l '::VATIN,
|
||||
'ESM0243487d'::VATIN,
|
||||
' ESb17616756 '::VATIN
|
||||
;
|
||||
vatin | vatin | vatin | vatin | vatin
|
||||
-------------+-------------+-------------+-------------+-------------
|
||||
ES40404040D | ES40404040D | ESX0523821L | ESM0243487D | ESB17616756
|
||||
(1 row)
|
||||
|
||||
-- Test invalid checksums
|
||||
SELECT 'ES40414040D'::VATIN;
|
||||
ERROR: invalid input syntax for VAT number: "ES40414040D"
|
||||
LINE 1: SELECT 'ES40414040D'::VATIN;
|
||||
^
|
||||
SELECT 'ESM0243487F'::VATIN;
|
||||
ERROR: invalid input syntax for VAT number: "ESM0243487F"
|
||||
LINE 1: SELECT 'ESM0243487F'::VATIN;
|
||||
^
|
||||
SELECT 'ESB17616757'::VATIN;
|
||||
ERROR: invalid input syntax for VAT number: "ESB17616757"
|
||||
LINE 1: SELECT 'ESB17616757'::VATIN;
|
||||
^
|
||||
-- Cleanup
|
||||
DROP EXTENSION vat;
|
|
@ -0,0 +1,21 @@
|
|||
--
|
||||
-- Test VATIN extensions
|
||||
--
|
||||
|
||||
CREATE EXTENSION vat;
|
||||
|
||||
-- Test valid conversions
|
||||
SELECT 'ES40404040D'::VATIN,
|
||||
'ES40404040-D'::VATIN,
|
||||
' esx0523821l '::VATIN,
|
||||
'ESM0243487d'::VATIN,
|
||||
' ESb17616756 '::VATIN
|
||||
;
|
||||
|
||||
-- Test invalid checksums
|
||||
SELECT 'ES40414040D'::VATIN;
|
||||
SELECT 'ESM0243487F'::VATIN;
|
||||
SELECT 'ESB17616757'::VATIN;
|
||||
|
||||
-- Cleanup
|
||||
DROP EXTENSION vat;
|
|
@ -0,0 +1,187 @@
|
|||
/* vatin--0.0.sql */
|
||||
|
||||
-- complain if script is sources in psql, rather than via CREATE EXTENSION
|
||||
\echo Use "CREATE EXTENSION vat" to load this file. \quit
|
||||
|
||||
-- Example:
|
||||
-- create table test ( id vatin );
|
||||
-- insert into tast values('ES40404040D');
|
||||
--
|
||||
-- select vatin('ES40404040-D');
|
||||
--
|
||||
|
||||
--
|
||||
-- Input and output functions and data types:
|
||||
--
|
||||
---------------------------------------------
|
||||
CREATE FUNCTION vatin_in(cstring)
|
||||
RETURNS vatin
|
||||
AS 'MODULE_PATHNAME'
|
||||
LANGUAGE C
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
CREATE FUNCTION vatin_out(vatin)
|
||||
RETURNS cstring
|
||||
AS 'textout'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
CREATE TYPE vatin (
|
||||
INPUT = vatin_in,
|
||||
OUTPUT = vatin_out,
|
||||
LIKE = pg_catalog.text
|
||||
);
|
||||
COMMENT ON TYPE vatin
|
||||
IS 'Value added tax identification number (VATIN)'
|
||||
;
|
||||
|
||||
--
|
||||
-- Operator functions:
|
||||
--
|
||||
---------------------------------------------
|
||||
CREATE FUNCTION vatinlt(vatin, vatin)
|
||||
RETURNS boolean
|
||||
AS 'text_lt'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
CREATE FUNCTION vatinle(vatin, vatin)
|
||||
RETURNS boolean
|
||||
AS 'text_le'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
CREATE FUNCTION vatineq(vatin, vatin)
|
||||
RETURNS boolean
|
||||
AS 'texteq'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE;
|
||||
CREATE FUNCTION vatinge(vatin, vatin)
|
||||
RETURNS boolean
|
||||
AS 'text_ge'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
CREATE FUNCTION vatingt(vatin, vatin)
|
||||
RETURNS boolean
|
||||
AS 'text_gt'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
CREATE FUNCTION vatinne(vatin, vatin)
|
||||
RETURNS boolean
|
||||
AS 'textne'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
|
||||
--
|
||||
-- Operators:
|
||||
--
|
||||
---------------------------------------------
|
||||
CREATE OPERATOR < (
|
||||
PROCEDURE = vatinlt,
|
||||
LEFTARG = vatin,
|
||||
RIGHTARG = vatin,
|
||||
COMMUTATOR = >,
|
||||
NEGATOR = >=,
|
||||
RESTRICT = scalarltsel,
|
||||
JOIN = scalarltjoinsel
|
||||
);
|
||||
CREATE OPERATOR <= (
|
||||
PROCEDURE = vatinle,
|
||||
LEFTARG = vatin,
|
||||
RIGHTARG = vatin,
|
||||
COMMUTATOR = >=,
|
||||
NEGATOR = >,
|
||||
RESTRICT = scalarltsel,
|
||||
JOIN = scalarltjoinsel
|
||||
);
|
||||
CREATE OPERATOR = (
|
||||
PROCEDURE = vatineq,
|
||||
LEFTARG = vatin,
|
||||
RIGHTARG = vatin,
|
||||
COMMUTATOR = =,
|
||||
NEGATOR = <>,
|
||||
RESTRICT = eqsel,
|
||||
JOIN = eqjoinsel,
|
||||
MERGES,
|
||||
HASHES
|
||||
);
|
||||
CREATE OPERATOR >= (
|
||||
PROCEDURE = vatinge,
|
||||
LEFTARG = vatin,
|
||||
RIGHTARG = vatin,
|
||||
COMMUTATOR = <=,
|
||||
NEGATOR = <,
|
||||
RESTRICT = scalargtsel,
|
||||
JOIN = scalargtjoinsel
|
||||
);
|
||||
CREATE OPERATOR > (
|
||||
PROCEDURE = vatingt,
|
||||
LEFTARG = vatin,
|
||||
RIGHTARG = vatin,
|
||||
COMMUTATOR = <,
|
||||
NEGATOR = <=,
|
||||
RESTRICT = scalargtsel,
|
||||
JOIN = scalargtjoinsel
|
||||
);
|
||||
CREATE OPERATOR <> (
|
||||
PROCEDURE = vatinne,
|
||||
LEFTARG = vatin,
|
||||
RIGHTARG = vatin,
|
||||
COMMUTATOR = <>,
|
||||
NEGATOR = =,
|
||||
RESTRICT = neqsel,
|
||||
JOIN = neqjoinsel
|
||||
);
|
||||
|
||||
--
|
||||
-- Operator families for the various operator classes:
|
||||
--
|
||||
---------------------------------------------
|
||||
CREATE OPERATOR FAMILY vatin_ops USING btree;
|
||||
CREATE OPERATOR FAMILY vatin_ops USING hash;
|
||||
|
||||
--
|
||||
-- Operator classes:
|
||||
--
|
||||
---------------------------------------------
|
||||
CREATE FUNCTION btvatincmp(vatin, vatin)
|
||||
RETURNS int4
|
||||
AS 'bttextcmp'
|
||||
LANGUAGE 'internal'
|
||||
IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
|
||||
CREATE OPERATOR CLASS vatin_ops DEFAULT
|
||||
FOR TYPE vatin USING btree FAMILY vatin_ops AS
|
||||
OPERATOR 1 <,
|
||||
OPERATOR 2 <=,
|
||||
OPERATOR 3 =,
|
||||
OPERATOR 4 >=,
|
||||
OPERATOR 5 >,
|
||||
FUNCTION 1 btvatincmp(vatin, vatin)
|
||||
;
|
||||
|
||||
CREATE FUNCTION hashvatin(vatin)
|
||||
RETURNS int4
|
||||
AS 'hashtext'
|
||||
LANGUAGE 'internal' IMMUTABLE STRICT
|
||||
PARALLEL SAFE
|
||||
;
|
||||
|
||||
CREATE OPERATOR CLASS vatin_ops DEFAULT
|
||||
FOR TYPE vatin USING hash FAMILY vatin_ops AS
|
||||
OPERATOR 1 =,
|
||||
FUNCTION 1 hashvatin(vatin)
|
||||
;
|
|
@ -0,0 +1,5 @@
|
|||
comment = 'data type for VAT registration numbers'
|
||||
default_version = '0.0'
|
||||
module_pathname = '$libdir/vat'
|
||||
relocatable = true
|
||||
superuser = false
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include <postgres.h>
|
||||
#include <fmgr.h>
|
||||
#include <utils/builtins.h>
|
||||
|
||||
#include "es.h"
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
static bool
|
||||
string2vatin(const char *str, text **result) {
|
||||
const char *r = str;
|
||||
char *vatin = NULL;
|
||||
size_t len;
|
||||
char country[2];
|
||||
int i;
|
||||
|
||||
for (; *r == ' '; r++);
|
||||
if (*r == 0) {
|
||||
goto invalidvatin;
|
||||
}
|
||||
len = strlen(r);
|
||||
for (; *(r + len - 1) == ' '; len--);
|
||||
if (len < 3) {
|
||||
goto invalidvatin;
|
||||
}
|
||||
|
||||
for(i = 0; i < 2; i++, r++, len--) {
|
||||
country[i] = toupper(*r);
|
||||
}
|
||||
if (country[0] == 'E' && country[1] == 'S') {
|
||||
vatin = check_es_vat(r, len);
|
||||
if (vatin == NULL) {
|
||||
goto invalidvatin;
|
||||
}
|
||||
} else {
|
||||
// For countries without validation yet assume it is correct
|
||||
vatin = pstrdup(r);
|
||||
}
|
||||
*result = (text *)palloc(2 + len + VARHDRSZ);
|
||||
SET_VARSIZE(*result, 2 + len + VARHDRSZ);
|
||||
memcpy(VARDATA(*result), country, 2);
|
||||
memcpy(VARDATA(*result) + 2, vatin, len);
|
||||
pfree(vatin);
|
||||
|
||||
return true;
|
||||
|
||||
invalidvatin:
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for VAT number: \"%s\"", str)));
|
||||
return false;
|
||||
}
|
||||
|
||||
PG_FUNCTION_INFO_V1(vatin_in);
|
||||
Datum
|
||||
vatin_in(PG_FUNCTION_ARGS)
|
||||
{
|
||||
const char *str = PG_GETARG_CSTRING(0);
|
||||
text *result = NULL;
|
||||
if (!string2vatin(str, &result)) {
|
||||
PG_RETURN_NULL();
|
||||
}
|
||||
PG_RETURN_TEXT_P(result);
|
||||
}
|
Loading…
Reference in New Issue