├── README.md ├── fd.pl ├── fd_parser.pl ├── fdc.pl ├── functional.pl ├── index.html ├── plunit_install.txt ├── sets.pl ├── start.bat ├── start.sh ├── test.pl ├── test.sh ├── timeout.pl └── ws.pl /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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([], _, []). -------------------------------------------------------------------------------- /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 |
73 |
74 |
75 | Input 76 |
77 | 78 | 79 |
80 |
81 | 82 | 83 |
84 |
85 | 86 |
87 |
88 | 89 |
90 | Results 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
normal form
fmin
keys
primary attributes
secondary attributes
BCNF decompositions
3NF decompositions
107 |
108 | 109 |
110 |
111 | 112 |

HTTP examples

113 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------