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