├── start.bat
├── start.sh
├── test.sh
├── functional.pl
├── fd_parser.pl
├── plunit_install.txt
├── sets.pl
├── timeout.pl
├── test.pl
├── fd.pl
├── index.html
├── README.md
├── ws.pl
└── fdc.pl
/start.bat:
--------------------------------------------------------------------------------
1 | swipl -f ws.pl -g start
2 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # switch working dir to install location
3 | cd $(dirname $0)
4 |
5 | swipl -f ws.pl -g start
6 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # switch working dir to install location
3 | cd $(dirname $0)
4 |
5 | swipl -f test.pl -t run_tests
6 |
--------------------------------------------------------------------------------
/functional.pl:
--------------------------------------------------------------------------------
1 | :- module(functional, [foldl/4, foldr/4, map/3]).
2 | % fold functions
3 | foldl([X|Xs], Pred, Y0, Y) :-
4 | call(Pred, X, Y0, Y1), foldl(Xs, Pred, Y1, Y).
5 | foldl([], _, Y, Y).
6 |
7 | foldr([X|Xs], Pred, Y0, Y) :-
8 | foldr(Xs, Pred, Y0, Y1), call(Pred, X, Y1, Y).
9 | foldr([], _, Y, Y).
10 |
11 | % mapping function
12 | map([X|Xs], Pred, [Y|Ys]) :-
13 | call(Pred, X, Y), map(Xs, Pred, Ys).
14 | map([], _, []).
--------------------------------------------------------------------------------
/fd_parser.pl:
--------------------------------------------------------------------------------
1 | :- module(fd_parser, [parse_fds/2, fds_to_string/2]).
2 | :- use_module(functional).
3 |
4 | % operator for readable FDs
5 | :- op(800, xfx, ->).
6 |
7 | strip_spaces(S, NoSpaces) :-
8 | atomic_list_concat(L, ' ', S),
9 | atomic_list_concat(L, NoSpaces).
10 |
11 | fd_string_to_list(S, L) :-
12 | strip_spaces(S, NoSpaces),
13 | atomic_list_concat(L, ',', NoSpaces).
14 |
15 | fds_to_string(L, S) :-
16 | map(L, fd_parser:fd_to_string, S0),
17 | atomic_list_concat(S0, ', ', S).
18 |
19 | fd_to_string(X->Y, S) :-
20 | atomic_list_concat([X,Y], '->', S).
21 |
22 | fdsplitter(S, FD) :-
23 | atomic_list_concat(FD0, '->', S),
24 | FD0 = [X,Y],
25 | FD = (X->Y).
26 |
27 | parse_fds(S, FD) :-
28 | fd_string_to_list(S, L), map(L, fd_parser:fdsplitter, FD).
29 |
30 |
--------------------------------------------------------------------------------
/plunit_install.txt:
--------------------------------------------------------------------------------
1 | # install the necessary packages
2 | sudo apt-get install autoconf
3 | sudo apt-get install make
4 |
5 | # make the plunit module
6 | git clone git://www.swi-prolog.org/home/pl/git/pl.git
7 | cd pl/
8 | git submodule status
9 | git submodule update --init packages/plunit/
10 | cd packages/plunit/
11 | autoconf
12 | ./configure
13 | sudo make install
14 |
15 | # if `swipl -v` returns a version lower than 6 (e.g. "SWI-Prolog version 5.10.1 for amd64"), you must update SWI-Prolog
16 |
17 | # SWI-Prolog update on Debian
18 | # to /etc/apt/preferences
19 | Package: swi-prolog
20 | Pin: release a=experimental
21 | Pin-Priority: 800
22 |
23 | Package: swi-prolog-nox
24 | Pin: release a=experimental
25 | Pin-Priority: 800
26 |
27 | Package: swi-prolog-x
28 | Pin: release a=experimental
29 | Pin-Priority: 800
30 |
31 | # to /etc/apt/sources.list
32 | deb http://ftp.debian.org/debian experimental main
33 |
34 | # update the packages
35 | sudo apt-get update
36 | sudo apt-get upgrade
37 |
--------------------------------------------------------------------------------
/sets.pl:
--------------------------------------------------------------------------------
1 | :- module(sets, [powerSet/2, subtract/3, union/3, union/2, subset/2]).
2 |
3 | % from the Erlang equivalent in http://dp.iit.bme.hu/dp-current/dp11a-minden-p1.pdf
4 | insAll(_, [], Zss, Zss).
5 | insAll(X, [Ys|Yss], Zss, Xss) :-
6 | insAll(X, Yss, [[X|Ys]|Zss], Xss).
7 |
8 | powerSet([], [[]]).
9 | powerSet([X|Xs], PS) :-
10 | powerSet(Xs, P),
11 | insAll(X, P, P, PS), !.
12 | % we need the cut (!) because only one solution is possible
13 | % -> Prolog should not backtrack
14 | % (the procedure is translated from an Erlang function and Erlang does not backtrack)
15 |
16 | % from SWI-Prolog's lists.pl
17 | subtract([], _, []) :- !.
18 | subtract([E|T], D, R) :-
19 | memberchk(E, D), !,
20 | subtract(T, D, R).
21 | subtract([H|T], D, [H|R]) :-
22 | subtract(T, D, R).
23 |
24 | union([], L, L) :- !.
25 | union([H|T], L, R) :-
26 | memberchk(H, L), !,
27 | union(T, L, R).
28 | union([H|T], L, [H|R]) :-
29 | union(T, L, R).
30 |
31 | union(Sets, USet) :-
32 | union2(Sets, [], USet).
33 |
34 | union2([], USetAcc, USetAcc).
35 | union2([H|T], USetAcc, USet) :-
36 | union(USetAcc, H, USetAcc0),
37 | union2(T, USetAcc0, USet).
38 |
39 | subset([], _) :- !.
40 | subset([E|R], Set) :-
41 | memberchk(E, Set),
42 | subset(R, Set).
43 |
--------------------------------------------------------------------------------
/timeout.pl:
--------------------------------------------------------------------------------
1 | :- module(timeout, [timeout/1, reply_with_timeout/2, call_with_timeout/5, call_with_timeout/6]).
2 | :- use_module(functional).
3 |
4 | % predicate for timeout constant (in seconds)
5 | timeout(2).
6 | small_timeout(0.1).
7 |
8 | % ========== timeout ==========
9 |
10 | % simple request with timeout
11 | reply_with_timeout(Request, Pred) :-
12 | timeout(T),
13 | catch(
14 | call_with_time_limit(T, (call(Pred, Request, Reply), HadTimeout = false)),
15 | time_limit_exceeded, % exception type
16 | (HadTimeout = true) % catch branch
17 | ),
18 | ( HadTimeout = true -> Reply = '[timeout]~n'
19 | ; true
20 | ),
21 | reply(Reply).
22 |
23 | % call functions with one argument, fallback to single solution
24 | call_with_timeout(A, Solution, PredNormal, PredTimeout, PredMapping) :-
25 | small_timeout(T), % using the _small_ timeout constant
26 | catch(
27 | call_with_time_limit(
28 | T,
29 | (
30 | call(PredNormal, A, Solution0),
31 | map(Solution0, PredMapping, Solution1),
32 | Solution = Solution1
33 | )
34 | ),
35 | time_limit_exceeded, % exception type
36 | ( % catch branch
37 | call(PredTimeout, A, Solution0),
38 | call(PredMapping, Solution0, Solution1),
39 | Solution2 = [Solution1],
40 | Solution = ['[timeout, only listing the first solution]'|Solution2],
41 | !
42 | )
43 | ).
44 |
45 | % call functions with two arguments
46 | call_with_timeout(A, B, Solution, PredNormal, PredTimeout, PredMapping) :-
47 | small_timeout(T), % using the _small_ timeout constant
48 | catch(
49 | call_with_time_limit(
50 | T,
51 | (
52 | call(PredNormal, A, B, Solution0),
53 | map(Solution0, PredMapping, Solution1),
54 | Solution = Solution1
55 | )
56 | ),
57 | time_limit_exceeded, % exception type
58 | ( % catch branch
59 | call(PredTimeout, A, B, Solution0),
60 | call(PredMapping, Solution0, Solution1),
61 | Solution2 = [Solution1],
62 | Solution = ['[timeout, only listing the first solution]'|Solution2],
63 | !
64 | )
65 | ).
66 |
67 |
--------------------------------------------------------------------------------
/test.pl:
--------------------------------------------------------------------------------
1 | :- use_module(fd).
2 | :- use_module(library(plunit)).
3 | :- begin_tests(fd).
4 |
5 | % normal forms
6 | test(nf01) :- nf(abc, [a->b], nf1NF).
7 | test(nf02) :- nf(lmno, [m->o, lm->ln, n->m, no->m], nf1NF).
8 | test(nf03) :- nf(abcd, [c->b, b->d, ab->ac, cd->b], nf1NF).
9 | test(nf04) :- nf(abcd, [ab->c, c->d], nf2NF).
10 | test(nf05) :- nf(vui, [vu->i, i->v], nf3NF).
11 | test(nf06) :- nf(abcdef, [a->b, b->c, c->a, d->e, e->f, f->d], nf3NF).
12 | test(nf07) :- nf(abc, [a->b, b->c, c->a], nfBCNF).
13 | test(nf08) :- nf(abcdef, [a->d, b->e, c->f, d->b, e->c, f->a], nfBCNF).
14 |
15 | % keys
16 | test(keys01) :- keys(abcdef, [a->b, b->c, c->a, d->e, e->f, f->d], [af, ae, ad, bf, be, bd, cf, ce, cd]).
17 |
18 | % primary attributes
19 | test(primaryattributes01) :- primaryattributes(abcd, [a->b, bc->ad], [a, b, c]).
20 | test(primaryattributes02) :- primaryattributes(abcd, [], [a, b, c, d]).
21 |
22 | % secondary attributes
23 | test(secondaryattributes01) :- secondaryattributes(abcd, [a->b, bc->ad], [d]).
24 | test(secondaryattributes02) :- secondaryattributes(abcd, [], []).
25 |
26 | % fmin
27 | test(fmin01) :-
28 | fmins_all(
29 | [a->b, ab->d, b->a, d->a],
30 | [[ (a->b), (b->d), (d->a)], [ (a->b), (a->d), (b->a), (d->a)]]
31 | ).
32 | test(fmin02) :-
33 | fmins_all(
34 | [cd->e, ab->cd, d->a, a->b, b->ac],
35 | [[ (a->b), (b->c), (b->d), (d->a), (d->e)],
36 | [ (a->b), (a->d), (b->a), (b->c), (d->a), (d->e)],
37 | [ (a->b), (a->c), (b->d), (d->a), (d->e)],
38 | [ (a->b), (a->c), (a->d), (b->a), (d->a), (d->e)]]
39 | ).
40 | test(fmin03) :- fmins_all(
41 | [abcd->e, e->d, a->b, ac->d],
42 | [[(a->b), (ac->e), (e->d)]]
43 | ).
44 |
45 | % d3nf
46 | test(d3nf01) :-
47 | d3nfs_all(
48 | abcde,
49 | [ab->b, b->c, c->bd],
50 | [[ace, bc, cd],
51 | [abe, bc, cd]]
52 | ).
53 |
54 | % bcnf
55 | test(bcnf01) :-
56 | bcnfs_all(
57 | abcde,
58 | [ab->b, b->c, c->bd],
59 | [[ab, abe, bc, bd],
60 | [abe, bc, bd],
61 | [abe, bc, bd, be],
62 | [ac, ace, bc, cd],
63 | [ace, bc, cd],
64 | [ace, bc, cd, ce],
65 | [ab, ace, bc, cd],
66 | [ab, abe, bc, cd],
67 | [abe, ac, bc, cd],
68 | [abe, bc, cd],
69 | [abe, bc, be, cd],
70 | [abe, bc, cd, ce],
71 | [ace, bc, be, cd]]
72 | ).
73 |
74 | :- end_tests(lists).
75 |
--------------------------------------------------------------------------------
/fd.pl:
--------------------------------------------------------------------------------
1 | :- module(fd, [nf/3, fmin/2, fmins_all/2, fequiv/2, keys/3, primaryattributes/3, secondaryattributes/3, fclose/3, bcnf/3, bcnfs_all/3, d3nf/3, d3nfs_all/3]).
2 | :- use_module(functional).
3 | :- use_module(fdc).
4 | :- use_module(library(lists)).
5 | :- use_module(library(time)).
6 | :- use_module(fd_parser).
7 |
8 | % convert atom to list of one-character atoms and backwards
9 | % myatom <---> [m, y, a, t, o, m]
10 | % [a] ----> add
11 | % a <---- a
12 | atom_to_list(A, L) :-
13 | ( atom(L) -> A = L
14 | ; atom_chars(A, L)
15 | ).
16 |
17 | list_of_atom_to_list([], []) :- !.
18 | list_of_atom_to_list([A|As], [L|Ls]) :-
19 | atom_to_list(A, L),
20 | list_of_atom_to_list(As, Ls).
21 |
22 | list_sort([], []).
23 | list_sort([H|T], [HS|TS]) :-
24 | sort(H, HS),
25 | list_sort(T, TS).
26 |
27 | % from pretty to canonical
28 | prettyFDs(FC, FP) :-
29 | map(FC, fd:prettyFD, FP).
30 |
31 | prettyFD(XL->YL, XA->YA) :-
32 | atom_to_list(XA, XL),
33 | atom_to_list(YA, YL).
34 |
35 | % from canonical to pretty
36 | canonicalFDs(FC, FP) :-
37 | map(FC, fd:canonicalFD, FP).
38 |
39 | canonicalFD(C, P) :-
40 | prettyFD(P, C).
41 |
42 | nf(R, F, N) :-
43 | atom_to_list(R, R0),
44 | canonicalFDs(F, F0),
45 | cSingleRightSide(F0, F1),
46 | cNF(R0, F1, N).
47 |
48 | % ========== ==========
49 |
50 | fequiv(F, G) :-
51 | canonicalFDs(F, F0),
52 | canonicalFDs(G, G0),
53 | cSingleRightSide(F0, F1),
54 | cSingleRightSide(G0, G1),
55 | cFequiv(F1, G1).
56 |
57 | keys(R, F, Keys) :-
58 | atom_to_list(R, R0),
59 | canonicalFDs(F, F0),
60 | cSingleRightSide(F0, F1),
61 | cKeys(R0, F1, Keys0),
62 | list_of_atom_to_list(Keys, Keys0).
63 |
64 | primaryattributes(R, F, PrimaryAttributes) :-
65 | atom_to_list(R, R0),
66 | canonicalFDs(F, F0),
67 | cSingleRightSide(F0, F1),
68 | cPrimaryAttributes(R0, F1, PrimaryAttributes0),
69 | sort(PrimaryAttributes0, PrimaryAttributes).
70 |
71 | secondaryattributes(R, F, SecondaryAttributes) :-
72 | atom_to_list(R, R0),
73 | canonicalFDs(F, F0),
74 | cSingleRightSide(F0, F1),
75 | cSecondaryAttributes(R0, F1, SecondaryAttributes0),
76 | sort(SecondaryAttributes0, SecondaryAttributes).
77 |
78 | % convert a schema decomposition (set of atoms) to readable text
79 | decomposition_to_text(L, L0) :-
80 | atomic_list_concat(L, ', ', L0).
81 |
82 | % calculate the closure of a set
83 | fclose(R, F, FClosed) :-
84 | atom_to_list(R, R0),
85 | canonicalFDs(F, F0),
86 | cSingleRightSide(F0, F1),
87 | cFclose(R0, F1, FClosed0),
88 | prettyFDs(FClosed0, FClosed).
89 |
90 | % ==================== prodecures that return multiple answers ====================
91 | % naming convention:
92 | % - proc: produces one answer, can backtrack (e.g. by hitting the ; key on the console)
93 | % - procs_all: produces all answers in a list
94 | % - procs: produces all answers in multiline text format
95 |
96 | % ========= BCNF ==========
97 | % decomposition to BCNF schemas
98 | bcnf(R, F, Rho) :-
99 | atom_to_list(R, R0),
100 | canonicalFDs(F, F0),
101 | cSingleRightSide(F0, F1),
102 | cBCNF(R0, F1, Rho0),
103 | list_of_atom_to_list(Rho, Rho0).
104 |
105 | % list all BCNF decompositions
106 | bcnfs_all(R, F, Rhos) :-
107 | findall(Rho, bcnf(R, F, Rho), Rhos).
108 |
109 | % ========== 3NF ==========
110 | % decomposition to 3NF schemas
111 | d3nf(R, F, Rho) :-
112 | atom_to_list(R, R0),
113 | canonicalFDs(F, F0),
114 | c3NF(R0, F0, Rho0),
115 | list_of_atom_to_list(Rho, Rho0).
116 |
117 | % aggregate all 3NF decompositons
118 | d3nfs_all(R, F, Rhos) :-
119 | findall(Rho, d3nf(R, F, Rho), Rhos).
120 |
121 | % ========= FMin ==========
122 | fmin(F, Fmin) :-
123 | canonicalFDs(F, F0),
124 | cFmin(F0, F1),
125 | prettyFDs(F1, Fmin).
126 |
127 | fmins_all(F, FMins) :-
128 | findall(FMin, fmin(F, FMin), FMins).
129 |
130 |
131 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Functional Dependency Calculator Web Service
5 |
6 |
7 |
8 |
66 |
67 |
68 |
69 |
70 | Functional Dependency Calculator Web Service
71 | AJAX form
72 |
111 |
112 | HTTP examples
113 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Functional Dependency Calculator
2 | ================================
3 | Gabor Szarnyas, Adam Lippai, 2012--2021.
4 |
5 | Little Prolog tool for performing simple algorithms on functional dependency sets.
6 |
7 | Given a relational scheme and a set of functional dependencies the program can
8 | - determine the highest normal form of a relational schema,
9 | - enumerate minimal covers of the FD set,
10 | - enumerate the keys of the relational schema,
11 | - determine the primary and secondary attributes of the relational schema,
12 | - enumerate lossless and dependency preserving 3NF or lossless BCNF decompositions of the schema.
13 |
14 | Compatible and tested with [SWI-Prolog](https://www.swi-prolog.org/). This Prolog implementation was chosen because of its module concept, its ability to run a HTTP server, exchange data in AJAX format and its unit testing framework.
15 |
16 | Architecture
17 | ------------
18 | ```
19 | ┌───────────────────────┐ ┌────────────────────────┐
20 | │ web frontend │ │ Prolog console │
21 | │ [e.g. HTML+AJAX page] │ │ [e.g. SWI─Prolog] │
22 | └───────────┬───────────┘ └────────────┬───────────┘
23 | │ │
24 | ┌───────────┴───────────┐ │
25 | │ web service (ws.pl) │ │
26 | │ [SWI-Prolog] │ │
27 | └───────────┬───────────┘ │
28 | │ │
29 | ┌───────────┴────────────────────────────┴───────────┐
30 | │ Functional Dependency Calculator frontend (fd.pl) │
31 | └─────────────────────────┬──────────────────────────┘
32 | │
33 | ┌─────────────────────────┴──────────────────────────┐
34 | │ Functional Dependency Calculator core (fdc.pl) │
35 | └────────────────────────────────────────────────────┘
36 | ```
37 |
38 | Each layer uses only lower layers so the web service, the frontend and the core layer may run without the higher ones.
39 |
40 | Usage
41 | -----
42 | In the SWI-Prolog console compile `fd.pl` (type `[fd].`).
43 |
44 | Functional dependencies are formatted as `ab->cd`. Prolog atoms must begin with a small letter so you should use small letters for each attribute.
45 |
46 | Enumerate minimal covers of a FD set (note that the relational schema is irrelevant in this case).
47 | ```
48 | ?- fmin([cd->e, ab->cd, d->a, a->b, b->ac], FMin).
49 | FMin = [ (a->b), (b->c), (b->d), (d->a), (d->e)] ;
50 | FMin = [ (a->b), (a->d), (b->a), (b->c), (d->a), (d->e)] ;
51 | FMin = [ (a->b), (a->c), (b->d), (d->a), (d->e)] ;
52 | FMin = [ (a->b), (a->c), (a->d), (b->a), (d->a), (d->e)] ;
53 | false.
54 | ```
55 |
56 | Determine the highest normal form of a relational schema:
57 | ```
58 | ?- nf(abcdef, [a->b, b->c, c->a, d->e, e->f, f->d], NF).
59 | NF = nf3NF.
60 | ```
61 | Enumerate the keys of a relational schema:
62 | ```
63 | ?- keys(abcdef, [a->b, b->c, c->a, d->e, e->f, f->d], Keys).
64 | Keys = [af, ae, ad, bf, be, bd, cf, ce, cd].
65 | ```
66 |
67 | Determine the primary and secondary attributes of a relational schema:
68 | ```
69 | ?- primaryattributes(abcd, [a->b, bc->ad], Primary).
70 | Primary = [a, b, c].
71 |
72 | ?- secondaryattributes(abcd, [a->b, bc->ad], Secondary).
73 | Secondary = [d].
74 | ```
75 |
76 | Enumerate lossless and dependency preserving 3NF...
77 | ```
78 | ?- d3nf(abcde, [ab->b, b->c, c->bd], Rho).
79 | Rho = [ace, bc, cd] ;
80 | Rho = [abe, bc, cd] ;
81 | false.
82 | ```
83 |
84 | ...or lossless BCNF decompositions of a schema:
85 | ```
86 | ?- bcnf(abcde, [ab->b, b->c, c->bd], Rho).
87 | Rho = [ab, abe, bc, bd] ;
88 | Rho = [abe, bc, bd] ;
89 | Rho = [abe, bc, bd, be] ;
90 | Rho = [ac, ace, bc, cd] ;
91 | Rho = [ace, bc, cd] ;
92 | Rho = [ace, bc, cd, ce] ;
93 | Rho = [ab, ace, bc, cd] ;
94 | Rho = [ab, abe, bc, cd] ;
95 | Rho = [abe, ac, bc, cd] ;
96 | Rho = [abe, bc, cd] ;
97 | Rho = [abe, bc, be, cd] ;
98 | Rho = [abe, bc, cd, ce] ;
99 | Rho = [ace, bc, be, cd] ;
100 | false.
101 | ```
102 | The BCNF decomposition algoritm may produce a _lot_ of possible decompositions even for schemas with only a few attributes.
103 |
104 | Starting the web service
105 | ------------------------
106 | - From SWI-Prolog: compile ```ws.pl``` then run ```start.```.
107 | - From command line: start the web service with the ```start.sh``` or ```start.bat``` script. Visit http://localhost:5000/ and try the examples. You may change the port by editing the ```port``` clause in ```ws.pl```.
108 |
109 | Running the unit tests
110 | ----------------------
111 | To run the unit tests, run ```test.sh```. Note that SWI-Prolog does not include ```plunit``` by default, see ```plunit_install.txt``` for details.
112 |
--------------------------------------------------------------------------------
/ws.pl:
--------------------------------------------------------------------------------
1 | :- use_module(library(http/thread_httpd)).
2 | :- use_module(library(http/http_dispatch)).
3 | :- use_module(library(http/html_write)).
4 | :- use_module(library(http/http_parameters)).
5 | :- use_module(library(uri)).
6 | :- use_module(fd_parser).
7 | :- use_module(fd).
8 | :- use_module(timeout).
9 | :- use_module(library(http/json)).
10 | :- use_module(library(http/json_convert)).
11 | :- use_module(library(http/http_json)).
12 |
13 | :- json_object
14 | analysis(
15 | nf,
16 | keys,
17 | primaryattributes,
18 | secondaryattributes,
19 | fmin,
20 | d3nf,
21 | bcnf
22 | ).
23 | :- json_object error(message).
24 |
25 | :- http_handler(root(.), httpIndex, []).
26 | :- http_handler(root(nf), httpNFTimeout, []).
27 | :- http_handler(root(keys), httpKeysTimeout, []).
28 | :- http_handler(root(primaryattributes), httpPrimaryAttributesTimeout, []).
29 | :- http_handler(root(secondaryattributes), httpSecondaryAttributesTimeout, []).
30 | :- http_handler(root(fmin), httpFMinTimeout, []).
31 | :- http_handler(root(bcnfs), httpBCNFsTimeout, []).
32 | :- http_handler(root(d3nfs), http3NFsTimeout, []).
33 | :- http_handler(root(json), httpJSONTimeout, []).
34 |
35 | httpIndex(Request) :- http_reply_file('index.html', [], Request).
36 | httpNFTimeout(Request) :- reply_with_timeout(Request, httpNF).
37 | httpKeysTimeout(Request) :- reply_with_timeout(Request, httpKeys).
38 | httpPrimaryAttributesTimeout(Request) :- reply_with_timeout(Request, httpPrimaryAttributes).
39 | httpSecondaryAttributesTimeout(Request) :- reply_with_timeout(Request, httpSecondaryAttributes).
40 | httpFMinTimeout(Request) :- reply_with_timeout(Request, httpFMin).
41 | httpBCNFsTimeout(Request) :- reply_with_timeout(Request, httpBCNFs).
42 | http3NFsTimeout(Request) :- reply_with_timeout(Request, http3NFs).
43 |
44 | % port number
45 | port(5000).
46 |
47 | start :-
48 | port(Port),
49 | server(Port).
50 |
51 | stop :-
52 | port(Port),
53 | http_stop_server(Port, []).
54 |
55 | restart :-
56 | stop, [ws], start.
57 |
58 | server(Port) :-
59 | http_server(http_dispatch, [port(Port)]).
60 |
61 | null_to_empty([], '') :- !.
62 | null_to_empty(PrimaryAttributes, PrimaryAttributes).
63 |
64 | reply(Text) :-
65 | format('Content-type: text/plain~n'),
66 | format('Access-Control-Allow-Origin: *~n'),
67 | format('Access-Control-Allow-Method: GET~n~n'),
68 | format(Text).
69 |
70 | % nf
71 | httpNF(Request, Reply) :-
72 | http_parameters(Request,
73 | [
74 | r(R, []),
75 | f(F, [])
76 | ]),
77 | parse_fds(F, F0),
78 | nf(R, F0, N),
79 | Reply = N.
80 |
81 | % fmin
82 | httpFMin(Request, Reply) :-
83 | http_parameters(Request,
84 | [
85 | f(F, [])
86 | ]),
87 | parse_fds(F, F0),
88 | fmins(F0, FMins0),
89 | atomic_list_concat(FMins0, '~n', FMins1),
90 | Reply = FMins1.
91 |
92 | % list all fmins with timeout
93 | fmins(F, FMins) :-
94 | call_with_timeout(F, FMins, fmins_all, fmin, fd_parser:fds_to_string).
95 |
96 | % keys
97 | httpKeys(Request, Reply) :-
98 | http_parameters(Request,
99 | [
100 | r(R, []),
101 | f(F, [])
102 | ]),
103 | parse_fds(F, F0),
104 | keys(R, F0, Keys),
105 | atomic_list_concat(Keys, ', ', S),
106 | Reply = S.
107 |
108 | % primary attributes
109 | httpPrimaryAttributes(Request, Reply) :-
110 | http_parameters(Request,
111 | [
112 | r(R, []),
113 | f(F, [])
114 | ]),
115 | parse_fds(F, F0),
116 | primaryattributes(R, F0, PrimaryAttributes),
117 | null_to_empty(PrimaryAttributes, PrimaryAttributes0),
118 | Reply = PrimaryAttributes0.
119 |
120 | % secondary attributes
121 | httpSecondaryAttributes(Request, Reply) :-
122 | http_parameters(Request,
123 | [
124 | r(R, []),
125 | f(F, [])
126 | ]),
127 | parse_fds(F, F0),
128 | secondaryattributes(R, F0, SecondaryAttributes),
129 | null_to_empty(SecondaryAttributes, SecondaryAttributes0),
130 | Reply = SecondaryAttributes0.
131 |
132 | % BCNF decompositions
133 | httpBCNFs(Request, Reply) :-
134 | http_parameters(Request,
135 | [
136 | r(R, []),
137 | f(F, [])
138 | ]),
139 | parse_fds(F, F0),
140 | bcnfs(R, F0, Rhos0),
141 | atomic_list_concat(Rhos0, '~n', Rhos1),
142 | Reply = Rhos1.
143 |
144 | % list all BCNF decompositions with timeout
145 | bcnfs(R, F, Rhos) :-
146 | call_with_timeout(R, F, Rhos, bcnfs_all, bcnf, fd:decomposition_to_text).
147 |
148 | % 3NF decompositions
149 | http3NFs(Request, Reply) :-
150 | http_parameters(Request,
151 | [
152 | r(R, []),
153 | f(F, [])
154 | ]),
155 | parse_fds(F, F0),
156 | d3nfs(R, F0, Rhos0),
157 | atomic_list_concat(Rhos0, '~n', Rhos1),
158 | Reply = Rhos1.
159 |
160 | % list all 3NF decompositions with timeout
161 | d3nfs(R, F, Rhos) :-
162 | call_with_timeout(R, F, Rhos, d3nfs_all, d3nf, fd:decomposition_to_text).
163 |
164 | httpJSONTimeout(Request) :-
165 | timeout(T),
166 | catch(
167 | call_with_time_limit(T, httpJSON(Request)),
168 | time_limit_exceeded, % exception type
169 | reply('timeout') % catch branch
170 | ).
171 |
172 | httpJSON(Request) :-
173 | http_parameters(Request,
174 | [
175 | r(R, []),
176 | f(F, [])
177 | ]),
178 | parse_fds(F, F0),
179 | nf(R, F0, N),
180 | keys(R, F0, Keys),
181 | primaryattributes(R, F0, PrimaryAttributes),
182 | secondaryattributes(R, F0, SecondaryAttributes),
183 | fmins(F0, FMins),
184 | d3nfs(R, F0, D3NFs),
185 | bcnfs(R, F0, BCNFs),
186 | prolog_to_json(analysis(N, Keys, PrimaryAttributes, SecondaryAttributes, FMins, D3NFs, BCNFs), Result),
187 | reply_json(Result).
188 |
--------------------------------------------------------------------------------
/fdc.pl:
--------------------------------------------------------------------------------
1 | :- module(fdc, [cSingleRightSide/2, cNF/3, cFmin/2, cFequiv/2, cKeys/3, cPrimaryAttributes/3, cSecondaryAttributes/3, cFclose/3, cBCNF/3, c3NF/3, cDecomposeTo3NF/3]).
2 | :- use_module(functional).
3 | :- use_module(sets).
4 | :- dynamic(leftred/1).
5 | :- dynamic(minimal/1).
6 | :- dynamic(bcnfdecomposition/1).
7 | :- dynamic(d3nfdecomposition/1).
8 |
9 | % operator for readable FDs
10 | :- op(800, xfx, ->).
11 |
12 | % the code relies heavily on backtracking, so it's worth noting
13 | % that the -> operator destroys choice-points created inside the clause.
14 | % so instead of
15 | % ( Condition -> Then
16 | % ; Else
17 | % )
18 | % we have to use
19 | % ( Condition, "Then"...
20 | % ; \+ Condition, "Else"...
21 | % )
22 | % to let backtrack work.
23 | % see also: http://www.swi-prolog.org/pldoc/man?predicate=send_arrow%2f2
24 |
25 | % XClosed = X+(F)
26 | cClose(X, F, XClosed) :-
27 | ( X = [] -> XClosed = []
28 | ; foldr(F, fdc:cExpand, X, X0),
29 | ( X = X0 -> XClosed = X0
30 | ; cClose(X0, F, XClosed)
31 | )
32 | ).
33 |
34 | % FD, set of attributes, expanded set of attributes
35 | cExpand(Y->B, X, XExpanded) :-
36 | ( subset(Y, X) -> union(X, [B], XExpanded) % Y is a subset of X
37 | ; XExpanded = X % cannot expand
38 | ).
39 |
40 | % X is a superkey if X+(F) = R
41 | % two sets are equivalent if both are a subset of the other.
42 | % the X+(F) is always a subset of R, so we need to check if R is a subset of X+(F)
43 | cSuperkey(R, F, X) :-
44 | cClose(X, F, XClosed), subset(R, XClosed).
45 |
46 | % X->A is trivial if A is an element of X
47 | cTrivial(X->A) :-
48 | memberchk(A, X).
49 |
50 | % S is the superkeys of schema R with FDs F
51 | cSuperKeys(R, F, S) :-
52 | powerSet(R, Hatv),
53 | findall(X, (member(X, Hatv), cSuperkey(R, F, X)), S).
54 |
55 | % a key K is minimal if no real subset of K is a superkey
56 | cMinimal(X, SuperKeys) :-
57 | \+ bagof(K, (member(K, SuperKeys), K \= X, subset(K, X)), _).
58 | % ^ looking for real subsets of X in the set of SuperKeys
59 | % if we found one, the superkey is not minimal -> the negation causes the clause to fail
60 |
61 | % key: 1) superkey 2) minimal
62 | cKeys(R, F, Keys) :-
63 | cSuperKeys(R, F, SuperKeys),
64 | findall(X, (member(X, SuperKeys), cMinimal(X, SuperKeys)), Keys).
65 |
66 | % set of primary attributes
67 | cPrimaryAttributes(R, F, PrimaryAttributes) :-
68 | cKeys(R, F, Keys),
69 | union(Keys, PrimaryAttributes).
70 |
71 | % set of secondary attributes
72 | cSecondaryAttributes(R, F, SecondaryAttributes) :-
73 | cPrimaryAttributes(R, F, PrimaryAttributes),
74 | subtract(R, PrimaryAttributes, SecondaryAttributes).
75 |
76 | % is primary attribute
77 | cPrimary(R, F, A) :-
78 | cPrimaryAttributes(R, F, PrimaryAttributes),
79 | memberchk(A, PrimaryAttributes).
80 |
81 | % ==================== BCNF ===================
82 | % for all X->A in we check if it satisfies BCNF
83 | cTestBCNF(R, F) :-
84 | findall(XA, (member(XA, F), cSatisfiesBCNF(R, F, XA)), L),
85 | subset(F, L).
86 |
87 | % X->A non-trivial FD satisfies BCNF if X is a superkey
88 | cSatisfiesBCNF(R, F, X->A) :-
89 | ( cTrivial(X->A)
90 | ; cSuperkey(R, F, X)
91 | ).
92 |
93 | % ==================== 3NF ====================
94 | cTest3NF(R, F) :-
95 | findall(XA, (member(XA, F), cSatisfies3NF(R, F, XA)), L),
96 | subset(F, L).
97 |
98 | % X->A non-trivial FD satisfies 3NF if X is a superkey or A is a primary attribute
99 | cSatisfies3NF(R, F, X->A) :-
100 | ( cTrivial(X->A)
101 | ; cSuperkey(R, F, X)
102 | ; cPrimary(R, F, A)
103 | ).
104 |
105 | % ==================== 2NF ====================
106 | cTest2NF(R, F) :-
107 | cKeys(R, F, Keys),
108 | cSecondaryAttributes(R, F, SecondaryAttributes),
109 | % collect the solutions of key->secondary attribute FDs
110 | \+ bagof(K->A, (member(K, Keys), member(A, SecondaryAttributes), \+ cSatisfies2NF(F, K->A)), _).
111 |
112 | % K->A (where K is a key, A is a secondary attribute) satisfies 2NF if no real subset X of K exist such that X->A
113 | cSatisfies2NF(F, K->A) :-
114 | powerSet(K, KSubsets),
115 | subtract(KSubsets, [K], KRealSubsets),
116 | \+ bagof(X, (member(X, KRealSubsets), cClose(X, F, XClosed), memberchk(A, XClosed)), _).
117 |
118 | % ============ highest normal form =============
119 | cNF(R, F, NF) :-
120 | ( cTestBCNF(R, F) -> NF = nfBCNF
121 | ; cTest3NF(R, F) -> NF = nf3NF
122 | ; cTest2NF(R, F) -> NF = nf2NF
123 | ; NF = nf1NF
124 | ).
125 |
126 | % 1st step of minimalizing
127 | % all FDs may have a single attribute on their right side
128 | cSingleRightSide(F, FFormatted) :-
129 | foldl(F, fdc:cDecompose, [], F0),
130 | lists:reverse(F0, FFormatted).
131 |
132 | % decomposing right side of a FD (consequence of Armstrong's axioms)
133 | cDecompose(X->Y, F, F1) :-
134 | ( Y = [A|Yt] -> cDecompose(X->Yt, [X->A|F], F1)
135 | ; F1 = F
136 | ).
137 |
138 | % 2nd step of minimalizing
139 | % omitting superfluous attributes from the left side of FDs
140 | cMinimalizeLeftSide(F, FLeftRed) :-
141 | retractall(leftred(_)),
142 | cMinimalizeLeftSide(F, F, FLeftRed, false).
143 |
144 | cMinimalizeLeftSide([], FLeftRed, FLeftRed, false) :-
145 | \+ leftred(FReduced), assert(leftred(FReduced)).
146 | cMinimalizeLeftSide([X->A|T], F, FLeftRed, SkippedFlag) :-
147 | ( cReducible(X->A, F, Y),
148 | subtract(F, [X->A], F0),
149 | union(F0, [Y->A], F1),
150 | ( cMinimalizeLeftSide(F1, FLeftRed)
151 | ; cMinimalizeLeftSide(T, F, FLeftRed, true)
152 | )
153 | ; \+ cReducible(X->A, F, Y), cMinimalizeLeftSide(T, F, FLeftRed, SkippedFlag)
154 | ).
155 |
156 | % 3rd step of minimalizing
157 | % skipping deducible FDs
158 | cSkipFDs(F, FMin) :-
159 | cSkipFDs(F, F, FMin, false).
160 |
161 | % we may skip X->A if A is in (X)+(G), where G is F \ {X->A}
162 | % cSkipFDs(tail of FDs, all FDs, minimalised FDs, reduced bit)
163 | cSkipFDs([], FReduced, FReduced, false).
164 | cSkipFDs([X->A|T], F, FReduced, SkippedFlag) :-
165 | subtract(F, [X->A], G), % G = F \ {X->A}
166 | cClose(X, G, XClosed), % calculating X+(G)
167 | ( memberchk(A, XClosed) -> % A is in X+(G)
168 | ( cSkipFDs(G, FReduced)
169 | ; cSkipFDs(T, F, FReduced, true)
170 | )
171 | ; cSkipFDs(T, F, FReduced, SkippedFlag)
172 | ).
173 |
174 | cReducible(X->A, F, Y) :-
175 | cLeftRed(X, X->A, F, Y).
176 |
177 | cLeftRed([H|T], X->A, F, Y) :-
178 | subtract(X, [H], X0),
179 | cClose(X0, F, X0C),
180 | ( memberchk(A, X0C), Y = X0
181 | ; cLeftRed(T, X->A, F, Y)
182 | ).
183 |
184 | cFmin(F, FMin) :-
185 | retractall(minimal(_)),
186 | cSingleRightSide(F, F1),
187 | cMinimalizeLeftSide(F1, F2),
188 | cSkipFDs(F2, F3),
189 | sort(F3, FMin),
190 | \+ minimal(FMin), assert(minimal(FMin)).
191 |
192 | cFsubset([], _G).
193 | cFsubset([X->A|T], G) :-
194 | cClose(X, G, XClosed),
195 | memberchk(A, XClosed),
196 | cFsubset(T, G).
197 |
198 | cFequiv(F, G) :-
199 | cFsubset(F, G),
200 | cFsubset(G, F).
201 |
202 | cNonTrivialClosures([], _, []).
203 | cNonTrivialClosures([H|T], F, FClosed) :-
204 | cNonTrivialClosures(T, F, TClosed),
205 | cClose(H, F, HClosed),
206 | subtract(HClosed, H, HClosedNonTrivial),
207 | ( HClosedNonTrivial = [] -> FClosed = TClosed
208 | ; FClosed = [H->HClosedNonTrivial|TClosed]
209 | ).
210 |
211 | cFclose(R, F, FClosed) :-
212 | powerSet(R, RP),
213 | cNonTrivialClosures(RP, F, FClosed0),
214 | cSingleRightSide(FClosed0, FClosed).
215 |
216 | cFilterProjected([], _, []).
217 | cFilterProjected([X->A|T], S, FF) :-
218 | cFilterProjected(T, S, TFF),
219 | ( memberchk(A, S) -> FF = [X->A|TFF]
220 | ; FF = TFF
221 | ).
222 |
223 | % FP is the set of projected FDs on F
224 | cProjectFDs(F, S, FP) :-
225 | cFclose(S, F, FP0),
226 | cFilterProjected(FP0, S, FP).
227 |
228 | % enumerate the next BCNF decomposition
229 | cBCNF(S, G, Rho) :-
230 | retractall(bcnfdecomposition(_)),
231 | cDecomposeToBCNF(S, G, Rho0),
232 | sort(Rho0, Rho),
233 | \+ bcnfdecomposition(Rho),
234 | assert(bcnfdecomposition(Rho)).
235 |
236 | % decompose to BCNF
237 | cDecomposeToBCNF(S, G, Rho) :-
238 | findall(XA, (member(XA, G), cSatisfiesBCNF(S, G, XA)), SatisfyingBCNF),
239 | subtract(G, SatisfyingBCNF, ViolatingBCNF), % find the FDs that violate the BCNF property
240 | ( member(X->A, ViolatingBCNF),
241 | (
242 | union(X, [A], S1),
243 | subtract(S, [A], S2),
244 | cProjectFDs(G, S1, G1),
245 | cProjectFDs(G, S2, G2),
246 | sort(S1, S10),
247 | sort(S2, S20),
248 | cDecomposeToBCNF(S10, G1, Rho1),
249 | cDecomposeToBCNF(S20, G2, Rho2),
250 | append(Rho1, Rho2, Rho)
251 | )
252 | ; ViolatingBCNF = [], Rho = [S]
253 | ).
254 |
255 | % enumerate the next BCNF decomposition
256 | c3NF(S, G, Rho) :-
257 | retractall(d3nfdecomposition(_)),
258 | cDecomposeTo3NF(S, G, Rho0),
259 | sort(Rho0, Rho),
260 | \+ d3nfdecomposition(Rho),
261 | assert(d3nfdecomposition(Rho)).
262 |
263 | % decompose to 3NF
264 | cDecomposeTo3NF(S, G, Rho) :-
265 | cSingleRightSide(G, G0),
266 | cKeys(S, G0, Keys), % determine the keys
267 | member(Key, Keys), % pick a key
268 | cFmin(G, GMin), % determine a possible minimal FD set
269 | map(GMin, fdc:cDependencyToSchema, Rho0), % convert the minimal FD set to relational schemas
270 | ( hasSuperSet(Rho0, Key) -> Rho = Rho0 % it contains the key, there is no need to add
271 | ; union(Rho0, [Key], Rho1), sort(Rho1, Rho)
272 | ).
273 |
274 | % converts a (canonical) minimal FD to relational schema,
275 | % e.g. [a, b]->e becomes [a, b, e]
276 | cDependencyToSchema(X->Y, R) :-
277 | union(X, [Y], R0),
278 | sort(R0, R).
279 |
280 | % returns if there is a set in a list of sets L which is a superset of S
281 | hasSuperSet(L, S) :-
282 | member(E, L), !,
283 | ( call(subset, S, E), !
284 | ; L = [_|T], hasSuperSet(T, S)
285 | ).
286 |
287 |
--------------------------------------------------------------------------------