Delete plpgsql directory

This commit is contained in:
Matt Decker 2022-06-26 09:17:49 -05:00 committed by GitHub
parent 032c768794
commit 472f891a04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 0 additions and 548 deletions

View File

@ -1,45 +0,0 @@
FROM postgres:12.1 as base
WORKDIR /app
ENV PGHOST=localhost
ENV PGDATABASE=kata
ENV PGPASSWORD=admin
ENV PGUSER=postgres
ENV POSTGRES_PASSWORD=admin
ENV PGDATA /var/lib/postgresql/data_local
RUN mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 777 "$PGDATA"
ADD ./*.sh /app/
ADD ./src/item.sql /app/src/
ADD ./src/new_item.sql /app/src/
# PGUNIT
FROM base as pgunit
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates wget \
&& rm -rf /var/lib/apt/lists/*
ADD ./pgunit/initialize.sh /app/
ADD ./pgunit/*.sql /app/
RUN chmod +x ./*.sh \
&& ./initializeDocker.sh
# PGTAP
FROM base as pgtap
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates build-essential git-core libv8-dev curl postgresql-server-dev-12 \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /tmp/pgtap \
&& cd /tmp/pgtap \
&& git clone https://github.com/theory/pgtap.git /tmp/pgtap \
&& make \
&& make install \
&& cpan TAP::Parser::SourceHandler::pgTAP
ADD ./pgtap/initialize.sh /app/
RUN chmod +x ./*.sh \
&& ./initializeDocker.sh

View File

@ -1,63 +0,0 @@
# Requirements
You'll need:
- PostgreSQL database, version >= 11 because PROCEDURE keyword isn't supported before [version 11](https://www.postgresql.org/docs/11/release-11.html)
- OS user with local connection privilege to database on standard 5432 port
To use remote / local dockerized database, add ``` --host --port --username``` parameters to plsql invocation.
# Setup
## With docker
Run `docker-compose up -d <TEST_FRAMEWORK>` to start, and `docker-compose exec <TEST_FRAMEWORK> bash` to enter in container.
`<TEST_FRAMEWORK>` is the testing framework name. Two values are possible: `pgunit` or `pgtap`
## Without docker
In shell:
- create database: ```createdb kata```
- create item table (structure): ```psql -d kata -f ./src/item.sql```
- create procedure to help to add item: ```psql -d kata -f ./src/new_item.sql```
- load code into database: ```psql -d kata -f ./src/update_quality.sql ```
If you get this message```LINE 1: CREATE OR REPLACE PROCEDURE public.update_quality()```, your PostgreSQL version may under 11, consider upgrading.
# Run
In shell:
- connect to CLI: ```psql -d kata```
- add item: ```CALL new_item('+5 Dexterity Vest', 10, 20);```
- check item state: ```SELECT * FROM item;```
- execute item update: ```CALL update_quality();```
- check item state: ```SELECT * FROM item;```
- empty table : ```TRUNCATE TABLE item;```
# Kata
`src/update_quality.sql` contains code to refactor.
# Test
## Using pgTAP
### Requirements
Install pgTAP [instructions here](https://pgtap.org/documentation.html#installation)
It's already installed with docker
```item``` table is supposed to be empty.
If not, it would cause a (false positive)[https://en.wikipedia.org/wiki/False_positives_and_false_negatives]
### Execute
Run `docker-compose up -d pgtap` to start, and `docker-compose exec pgtap bash` to enter in container.
In shell, execute ```psql -d kata -f src/update_quality.sql && pg_prove pgtap/test_*.sql```.
You should get ```pgtap/test_case_update_quality.sql .. ok All tests successful.```
If you get this message ```ERROR: function plan(integer) does not exist LINE 1: SELECT PLAN(1);```, pgTAP is not working => check your pgTAP installation.
If you get this message ```Failed test: 2 Parse errors: Bad plan. You planned 1 tests but ran 2.```, the item table contains data, interfering with the test => empty it, then run test again.
`pgtap/test_case_update_quality.sql` contains test examples.
## Using pgunit
### Requirement
Unit test framework used : pgunit (https://github.com/adrianandrei-ca/pgunit)
It's already installed with docker
### Execute
Run `docker-compose up -d pgunit` to start, and `docker-compose exec pgunit bash` to enter in container.
You can run `cat src/update_quality.sql pgunit/run_tests.sql | psql -d kata -f -`
`pgunit/run_tests.sql` contains test examples.

View File

@ -1,22 +0,0 @@
version: '3.4'
services:
pgunit:
# image: fpellet/gildedrose-refactoring-kata:pgunit
build:
context: .
target: pgunit
ports:
- "5432:5432"
volumes:
- .:/app/:z
pgtap:
# image: fpellet/gildedrose-refactoring-kata:pgtap
build:
context: .
target: pgtap
ports:
- "5432:5432"
volumes:
- .:/app/:z

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -ex
echo "Create database"
psql -d postgres -c 'DROP DATABASE IF EXISTS kata;'
psql -d postgres -c 'CREATE DATABASE kata;'
./initialize.sh
echo "Add current code"
psql -d kata -f src/item.sql
psql -d kata -f src/new_item.sql

View File

@ -1,26 +0,0 @@
#!/usr/bin/env bash
wait_database()
{
while :
do
(echo > /dev/tcp/127.0.0.1/5432) >/dev/null 2>&1
result=$?
if [[ $result -eq 0 ]]; then
break
fi
sleep 1
done
return $result
}
nohup docker-entrypoint.sh postgres > /dev/null 2>&1 &
wait_database
set -ex
./initializeDatabase.sh
echo "Stop database"
disown %1

View File

@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -ex
echo "Enable extension"
psql -d kata -c 'CREATE EXTENSION IF NOT EXISTS pgtap;'

View File

@ -1,18 +0,0 @@
BEGIN;
-- Plan count should match the number of tests. If it does not then pg_prove will fail the test
SELECT plan(1);
-- Run the tests.
-- Given
TRUNCATE TABLE item;
CALL new_item('foo', 0, 0);
-- When
CALL update_quality();
-- Then
SELECT is( name, 'fixme', 'name did change' ) FROM item;
-- Finish the tests and clean up.
SELECT * FROM finish();
ROLLBACK;

View File

@ -1,59 +0,0 @@
CREATE OR REPLACE FUNCTION test_assertEquals(message TEXT, expected ANYELEMENT, result ANYELEMENT) RETURNS VOID AS $$
BEGIN
IF expected = result THEN
null;
ELSE
raise exception '%: expect ''%'' instead of ''%''', message, expected, result using errcode = 'triggered_action_exception';
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_assertEquals(expected ANYELEMENT, result ANYELEMENT) RETURNS VOID AS $$
BEGIN
perform test_assertEquals('assertEquals failure', expected, result);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_assertEqualsArray(expected VARCHAR[], result VARCHAR[]) RETURNS VOID AS $$
DECLARE
line RECORD;
error_message text;
BEGIN
IF expected = result THEN
null;
ELSE
error_message := 'assertEqualsArray failure:';
FOR line IN SELECT expected_item, result_item FROM (SELECT unnest(expected) AS expected_item, unnest(result) AS result_item) x
LOOP
IF line.expected_item = line.result_item THEN
error_message := CONCAT(error_message, E'\n', '= ', line.expected_item);
ELSE
error_message := CONCAT(error_message, E'\n', '- ', line.expected_item);
error_message := CONCAT(error_message, E'\n', '+ ', line.result_item);
END IF;
END LOOP;
raise exception '%', error_message using errcode = 'triggered_action_exception';
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_assertEquals_golden_master(expected VARCHAR[], result VARCHAR[]) RETURNS VOID as $$
DECLARE
golden TEXT;
line VARCHAR;
BEGIN
perform test_assertEqualsArray(expected, result);
EXCEPTION
WHEN triggered_action_exception THEN
golden := CONCAT(SQLERRM, E'\n\n', E'For update, copy:\n');
golden := CONCAT(golden, E'expected := ARRAY[');
FOREACH line IN ARRAY result
LOOP
golden := CONCAT(golden, E'\n', '''', line, ''',');
END LOOP;
golden := CONCAT(golden, E'\n', '];');
raise exception '%', golden using errcode = 'triggered_action_exception';
END;
$$ LANGUAGE plpgsql;

View File

@ -1,95 +0,0 @@
CREATE OR REPLACE FUNCTION test_case_assertEquals_numeric_should_fail_if_not_equals() RETURNS VOID AS $$
DECLARE
expected_message VARCHAR;
error_message VARCHAR;
is_equals BOOLEAN;
BEGIN
BEGIN
perform test_assertEquals(7, 5);
EXCEPTION
WHEN triggered_action_exception THEN
expected_message := 'assertEquals failure: expect ''7'' instead of ''5''';
error_message := SQLERRM;
perform test_assertTrue(format('Expect message ''%s'' instead of ''%s'' ', expected_message, error_message), error_message = expected_message);
END;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_assertEquals_numeric_should_success_if_equals() RETURNS VOID AS $$
BEGIN
perform test_assertEquals(7, 7);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_assertEquals_text_should_fail_if_not_equals() RETURNS VOID AS $$
DECLARE
expected_message VARCHAR;
error_message VARCHAR;
is_equals BOOLEAN;
BEGIN
BEGIN
perform test_assertEquals('hello'::VARCHAR, 'olleh');
EXCEPTION
WHEN triggered_action_exception THEN
expected_message := 'assertEquals failure: expect ''hello'' instead of ''olleh''';
error_message := SQLERRM;
perform test_assertTrue(format('Expect message ''%s'' instead of ''%s'' ', expected_message, error_message), error_message = expected_message);
END;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_assertEquals_text_should_success_if_equals() RETURNS VOID AS $$
BEGIN
perform test_assertEquals('hello'::VARCHAR, 'hello');
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_assertEquals_should_display_custom_message_if_defined() RETURNS VOID AS $$
DECLARE
expected_message VARCHAR;
error_message VARCHAR;
is_equals BOOLEAN;
BEGIN
BEGIN
perform test_assertEquals('Test with custom message', 'hello'::VARCHAR, 'olleh');
EXCEPTION
WHEN triggered_action_exception THEN
expected_message := 'Test with custom message: expect ''hello'' instead of ''olleh''';
error_message := SQLERRM;
perform test_assertTrue(format('Expect message ''%s'' instead of ''%s'' ', expected_message, error_message), error_message = expected_message);
END;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_assertEqualsArray_should_success_if_equals() RETURNS VOID AS $$
BEGIN
perform test_assertEqualsArray(ARRAY['1','2'], ARRAY['1','2']);
perform test_assertEqualsArray(ARRAY['a','b'], ARRAY['a','b']);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_assertEqualsArray_should_display_diff_if_failed() RETURNS VOID AS $$
DECLARE
expected_message VARCHAR;
error_message VARCHAR;
is_equals BOOLEAN;
BEGIN
BEGIN
perform test_assertEqualsArray(ARRAY['1','2','3','4'], ARRAY['1','2','4','4']);
EXCEPTION
WHEN triggered_action_exception THEN
expected_message := CONCAT(
'assertEqualsArray failure:', E'\n',
'= 1', E'\n',
'= 2', E'\n',
'- 3', E'\n',
'+ 4', E'\n',
'= 4'
);
error_message := SQLERRM;
perform test_assertTrue(format('Expect message ''%s'' instead of ''%s'' ', expected_message, error_message), error_message = expected_message);
END;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM test_run_all();

View File

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -ex
echo "Enable DBLINK"
psql -d kata -c 'CREATE EXTENSION DBLINK;'
echo "Initialize test framework"
wget https://raw.githubusercontent.com/adrianandrei-ca/pgunit/bc69dfc526ec3db55ff72af5d78eab55661502af/PGUnit.sql \
&& psql -d kata -f PGUnit.sql \
&& rm PGUnit.sql
echo "Initialize custom asserts"
psql -d kata -f asserts.sql

View File

@ -1,95 +0,0 @@
CREATE OR REPLACE FUNCTION test_case_update_quality() RETURNS void AS $$
DECLARE
name_result item.name%TYPE;
BEGIN
TRUNCATE TABLE item;
CALL new_item('foo', 0, 0);
CALL update_quality();
SELECT name FROM item INTO name_result;
perform test_assertEquals('name did change', 'fixme', name_result);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION format_day(day INTEGER) RETURNS TEXT[] AS $$
DECLARE
result TEXT[];
item_result RECORD;
BEGIN
result := ARRAY[CONCAT('-------- day ', day, ' --------')];
result := result || 'name, sellIn, quality'::TEXT;
FOR item_result IN (SELECT name, sell_in, quality FROM item ORDER BY name ASC, sell_in ASC, quality ASC)
LOOP
result := result || format('%s, %s, %s', item_result.name, item_result.sell_in, item_result.quality);
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION test_case_update_quality_golden_master() RETURNS VOID AS $$
DECLARE
sell_in_result item.sell_in%TYPE;
quality_result item.quality%TYPE;
days INTEGER;
result TEXT[];
expected TEXT[];
item_result RECORD;
BEGIN
-- given
TRUNCATE TABLE item;
CALL new_item('+5 Dexterity Vest', 10, 20);
CALL new_item('Aged Brie', 2, 0);
CALL new_item('Elixir of the Mongoose', 5, 7);
CALL new_item('Sulfuras, Hand of Ragnaros', 0, 80);
CALL new_item('Sulfuras, Hand of Ragnaros', -1, 80);
CALL new_item('Backstage passes to a TAFKAL80ETC concert', 15, 20);
CALL new_item('Backstage passes to a TAFKAL80ETC concert', 10, 49);
CALL new_item('Backstage passes to a TAFKAL80ETC concert', 5, 49);
-- this conjured item does not work properly yet ;
CALL new_item('Conjured Mana Cake', 3, 6);
days := 1;
-- when
result := format_day(0);
FOR current_day IN 1 .. days
LOOP
CALL update_quality();
result := result || format_day(current_day);
END LOOP;
-- then
expected := ARRAY[
'-------- day 0 --------',
'name, sellIn, quality',
'+5 Dexterity Vest, 10, 20',
'Aged Brie, 2, 0',
'Backstage passes to a TAFKAL80ETC concert, 5, 49',
'Backstage passes to a TAFKAL80ETC concert, 10, 49',
'Backstage passes to a TAFKAL80ETC concert, 15, 20',
'Conjured Mana Cake, 3, 6',
'Elixir of the Mongoose, 5, 7',
'Sulfuras, Hand of Ragnaros, -1, 80',
'Sulfuras, Hand of Ragnaros, 0, 80',
'-------- day 1 --------',
'name, sellIn, quality',
'+5 Dexterity Vest, 9, 19',
'Aged Brie, 1, 1',
'Backstage passes to a TAFKAL80ETC concert, 4, 50',
'Backstage passes to a TAFKAL80ETC concert, 9, 50',
'Backstage passes to a TAFKAL80ETC concert, 14, 21',
'Conjured Mana Cake, 2, 5',
'Elixir of the Mongoose, 4, 6',
'Sulfuras, Hand of Ragnaros, -1, 80',
'Sulfuras, Hand of Ragnaros, 0, 80'
];
perform test_assertEquals_golden_master(expected, result);
END;
$$ LANGUAGE plpgsql;
SELECT * FROM test_run_all();

View File

@ -1,8 +0,0 @@
DROP TABLE IF EXISTS item;
CREATE TABLE item
(
name character varying(100) NOT NULL,
sell_in numeric(6) NOT NULL,
quality numeric(6) NOT NULL
);

View File

@ -1,12 +0,0 @@
DROP PROCEDURE IF EXISTS new_item;
CREATE PROCEDURE new_item(
name item.name%TYPE,
sell_in item.sell_in%TYPE,
quality item.quality%TYPE
)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO item (name, sell_in, quality) VALUES (name, sell_in, quality);
END;
$$;

View File

@ -1,72 +0,0 @@
CREATE OR REPLACE PROCEDURE update_quality()
LANGUAGE plpgsql
AS $$
DECLARE
c_items CURSOR FOR
SELECT name, sell_in, quality FROM item FOR UPDATE;
l_item RECORD;
l_name item.name%TYPE;
l_sell_in item.sell_in%TYPE;
l_quality item.quality%TYPE;
BEGIN
OPEN c_items;
LOOP
FETCH c_items INTO l_item;
EXIT WHEN NOT FOUND;
l_name := l_item.name;
l_sell_in := l_item.sell_in;
l_quality := l_item.quality;
IF l_name <> 'Aged Brie' AND l_name <> 'Backstage passes to a TAFKAL80ETC concert' THEN
IF l_quality > 0 THEN
IF l_name <> 'Sulfuras, Hand of Ragnaros' THEN
l_quality := l_quality - 1;
END IF;
END IF;
ELSE
IF (l_quality < 50) THEN
l_quality := l_quality + 1;
IF l_name = 'Backstage passes to a TAFKAL80ETC concert' THEN
IF l_sell_in < 11 THEN
IF l_quality < 50 THEN
l_quality := l_quality + 1;
END IF;
END IF;
IF l_sell_in < 6 THEN
IF l_quality < 50 THEN
l_quality := l_quality + 1;
END IF;
END IF;
END IF;
END IF;
END IF;
IF l_name <> 'Sulfuras, Hand of Ragnaros' THEN
l_sell_in := l_sell_in - 1;
END IF;
IF l_sell_in < 0 THEN
IF l_name <> 'Aged Brie' THEN
IF l_name <> 'Backstage passes to a TAFKAL80ETC concert' THEN
IF l_quality > 0 THEN
IF l_name <> 'Sulfuras, Hand of Ragnaros' THEN
l_quality := l_quality - 1;
END IF;
END IF;
ELSE
l_quality := l_quality - l_quality;
END IF;
ELSE
IF l_quality < 50 THEN
l_quality := l_quality + 1;
END IF;
END IF;
END IF;
UPDATE item
SET name = l_name, sell_in = l_sell_in, quality = l_quality WHERE CURRENT OF c_items;
END LOOP;
CLOSE c_items;
END;
$$;