Credit card business logic
CREATE TABLE biz (
cardnumber CHAR(16) NOT NULL
);
oscon=> INSERT INTO biz VALUES ('ABCDABCDABCDABCD');
INSERT 25460 1
oscon=> INSERT INTO biz VALUES ('1234567');
INSERT 25461 1
oscon=> INSERT INTO biz VALUES ('12345678901234567890');
ERROR: value too long for type character(16)
|
- First: numbers are 16 digits long
- First problem: length
|

Second attempt:
CREATE TABLE biz (
cardnumber VARCHAR(16) NOT NULL CHECK (length(cardnumber) = 16)
);
oscon=> INSERT INTO biz VALUES ('1234567');
ERROR: new row for relation "biz" violates check constraint "biz_cardnumber"
|
- Switch to VARCHAR (padding)
- Add a (third) constraint
- Three?
|

Better, but awkward.
CREATE DOMAIN creditcardnumber VARCHAR(16) CHECK (length(VALUE) = 16);
CREATE TABLE biz (
cardnumber creditcardnumber
);
oscon=> INSERT INTO biz VALUES (null);
INSERT 25462 1
oscon=> INSERT INTO biz VALUES ('1234567');
ERROR: value for domain creditcardnumber violates check constraint "$1"
|
- Time for a domain
- Not truly a custom data type
- Where is NOT NULL?
- CHECK is happy on TRUE or NULL.
- What's still wrong?
|

Better
CREATE DOMAIN ccnumber varchar(16)
CONSTRAINT "ccnumber_length"
CHECK (VALUE IS NOT NULL AND length(VALUE) = 16);
oscon=> INSERT INTO biz VALUES ('1234567');
ERROR: value for domain creditcardnumber v. check constraint "ccnumber_length"
|
- Good, but that CHECK is still too specific
- Make it into a function
- Allows us to reuse code elsewhere
- Allows us to extend logic without driopping tables
- Allows us to customize error messages
|

CREATE OR REPLACE FUNCTION validate_ccnumber(VARCHAR)
LANGUAGE plpgsql
RETURNS BOOLEAN AS '
DECLARE
mynumber ALIAS FOR $1;
BEGIN
IF length(mynumber) != 16 THEN
RAISE EXCEPTION \'Credit card numbers must be 16 digits long\';
END IF;
RETURN TRUE;
END;'
-- We have a nice "service function" - not coupled to any data
-- Very useful, very portable
-- Ignore NULL for now

oscon=> SELECT validate_ccnumber('12341234123412341');
ERROR: Credit card numbers must be 16 digits long

CREATE DOMAIN creditcardnumber varchar(16) CHECK (validate_ccnumber(VALUE));
CREATE TABLE biz2 (
cardnumber creditcardnumber,
oldway VARCHAR(16)
);
CREATE TABLE biz2 (
cardnumber CREDITCARDNUMBER,
oldway VARCHAR(16)
);

oscon=> INSERT INTO biz2 VALUES ('1234567');
ERROR: Credit card numbers must be 16 digits long

-- Avoiding the danger of domains by using black boxes
oscon=> CREATE OR REPLACE DOMAIN creditcardnumber varchar(16) check(length(value)=20);
ERROR: syntax error at or near "DOMAIN" at character 19
oscon=> DROP DOMAIN creditcardnumber;
NOTICE: table biz column cardnumber depends on type creditcardnumber
ERROR: cannot drop type creditcardnumber because other objects depend on it
HINT: Use DROP ... CASCADE to drop the dependent objects too.
oscon=> DROP DOMAIN creditcardnumber;
DROP DOMAIN
CREATE OR REPLACE FUNCTION validate_ccnumber(VARCHAR)
RETURNS BOOLEAN AS '
DECLARE
mynumber ALIAS FOR $1;
BEGIN
IF length(mynumber) != 12 THEN
RAISE EXCEPTION \'Credit card numbers must be 12 digits long\';
END IF;
RETURN TRUE;
END;
' LANGUAGE plpgsql;

Warning
 |
- No revalidation
- Be careful when switching types mid-stream
- Documentation mantra
|