453 lines
14 KiB
Prolog
453 lines
14 KiB
Prolog
/**
|
||
* Authors:
|
||
* Dubois Brieuc
|
||
* Dubois Simon
|
||
*/
|
||
|
||
:- dynamic tables/0.
|
||
/*
|
||
Prints the names of all existing tables, one per line (using writeln/1).
|
||
A table name is always an atom.
|
||
*/
|
||
tables :- tabl(X, _), writeln(X).
|
||
|
||
:- dynamic tables/1.
|
||
/*
|
||
Unify Tables with a list of the names of all existing tables.
|
||
*/
|
||
tables(Tables) :- findall(X, tabl(X, _), Tables).
|
||
|
||
:- dynamic create_table/2.
|
||
/*
|
||
When this predicate is executed, the effect will be the creation of a new
|
||
table with the specified list of column names (order matters!).
|
||
A column name is always an atom.
|
||
If a table with the given name already exists, the predicate throws a
|
||
descriptive exception (using throw/1).
|
||
*/
|
||
create_table(Table, Cols) :-
|
||
(tabl(Table, _) -> throw("Table already exists"));
|
||
assertz(tabl(Table, Cols)).
|
||
|
||
:- dynamic cols/2.
|
||
/*
|
||
Unifies Cols with the list of columns for the specified table (in the same
|
||
order as they were supplied to create table/2).
|
||
If the given table does not exist, the predicate must throw a descriptive
|
||
exception (use throw/1).
|
||
*/
|
||
cols(Table, Cols) :-
|
||
(tabl(Table, _) -> tabl(Table, Cols));
|
||
throw("Table doesn't exist").
|
||
|
||
:- dynamic row/2.
|
||
/*
|
||
Unifies Row, one result at a time, with each row in the given Table.
|
||
If the given table does not exist, the predicate should fail.
|
||
*/
|
||
row(Table, _) :-
|
||
\+ tabl(Table, _),
|
||
throw("Table doesn't exist"),
|
||
!.
|
||
|
||
row(Table, _) :-
|
||
tabl(Table, Cols),
|
||
length(Cols, L),
|
||
\+ current_predicate(Table/L),
|
||
!.
|
||
|
||
row(Table, Row) :-
|
||
tabl(Table, Cols),
|
||
length(Cols, L),
|
||
length(Row, L),
|
||
apply(Table, Row).
|
||
|
||
:- dynamic rows/1.
|
||
/*
|
||
Displays all rows in the given table, one per line (using writeln/1).
|
||
If the given table does not exist, the predicate throws a descriptive
|
||
exception.
|
||
*/
|
||
rows(Table) :-
|
||
\+ tabl(Table, _),
|
||
throw(table_does_not_exist(Table)),
|
||
!.
|
||
|
||
rows(Table) :-
|
||
tabl(Table, Cols),
|
||
length(Cols, L),
|
||
\+ current_predicate(Table/L),
|
||
!.
|
||
|
||
rows(Table) :-
|
||
row(Table, Row),
|
||
writeln(Row),
|
||
fail.
|
||
rows(_).
|
||
|
||
:- dynamic insert/2.
|
||
/*
|
||
When this predicate is executed, the effect will be the addition of a given
|
||
row in the given table. The given row is a list of values for each of the
|
||
corresponding columns in the table (in the order in which the columns
|
||
were supplied to create table/2).
|
||
If the given table does not exist, the predicate throws a descriptive
|
||
exception.
|
||
If the row does not have as many elements as the number of columns in
|
||
the table, the predicate throws a descriptive exception.
|
||
*/
|
||
insert(Table, Row) :-
|
||
(tabl(Table, Cols) ->
|
||
(length(Row, L1), length(Cols, L2), L1 =:= L2 ->
|
||
Term =.. [Table | Row],
|
||
assertz(Term);
|
||
throw("Row doesn't have as many elements as the number of columns in the table")
|
||
);
|
||
throw("Table doesn't exist")
|
||
).
|
||
|
||
:- dynamic drop/1.
|
||
/*
|
||
When this predicate is executed, the effect will be the deletion of the given
|
||
table.
|
||
If the given table does not exist, the predicate throws a descriptive
|
||
exception.
|
||
*/
|
||
drop(Table) :-
|
||
(tabl(Table, _) ->
|
||
delete(Table), retract(tabl(Table, _));
|
||
throw("Table doesn't exist")
|
||
).
|
||
|
||
:- dynamic delete/1.
|
||
/*
|
||
When this predicate is executed, the effect will be the deletion of all rows
|
||
in the given table. The table itself should still exist after, but with no
|
||
more rows.
|
||
If the given table does not exist, the predicate throws a descriptive
|
||
exception.
|
||
*/
|
||
delete(Table) :-
|
||
(tabl(Table, _) ->
|
||
tabl(Table, C), length(C, L), call(abolish, Table, L);
|
||
throw("Table doesn't exist")
|
||
).
|
||
|
||
:- dynamic delete/2.
|
||
/*
|
||
When this predicate is executed, the effect will be the deletion of all rows
|
||
from the given table that match all of the given conditions. The table
|
||
still exist after.
|
||
If the given table does not exist, the predicate throws a descriptive
|
||
exception.
|
||
A condition is any Prolog predicate that could have been typed at the
|
||
prompt, but which may include selectors. Selectors are terms of the form
|
||
+<column>where <column>should be replaced by a column name.
|
||
*/
|
||
delete(Table, Conds) :-
|
||
(tabl(Table, _) ->
|
||
row(Table, Row),
|
||
does_match(Conds, Table, Row),
|
||
Term =.. [Table | Row],
|
||
retract(Term),
|
||
fail
|
||
;
|
||
throw("Table doesn't exist")
|
||
).
|
||
|
||
delete(_, _).
|
||
|
||
|
||
does_match([], _, _) :- !.
|
||
|
||
does_match([Cond|Rest], Table, Row) :-
|
||
% Extract the operator, field and value from the condition
|
||
Cond =.. [Operator | T],
|
||
[+F, S] = T,
|
||
|
||
% Get the columns of the table
|
||
tabl(Table, Cols),
|
||
|
||
% Get the index of the field in the columns
|
||
member(F, Cols),
|
||
nth0(Index, Cols, F),
|
||
|
||
% Get the value of the field in the row
|
||
nth0(Index, Row, Value),
|
||
|
||
% Apply the operator to the value and the selector
|
||
apply(Operator, [Value, S]),
|
||
|
||
% Check the next condition
|
||
does_match(Rest, Table, Row).
|
||
|
||
|
||
:- dynamic selec/4.
|
||
/*
|
||
Table is the name of a single table.
|
||
Selectors is either * or a list of selectors. These define the resulting
|
||
projection. * means: select all column names from the table. Other
|
||
selectors explicitly specify which columns to pick.
|
||
For example, +name would select the column named
|
||
name.
|
||
Conds has the same form as in delete/2 and works the same way: only
|
||
rows that match all conditions are selected.
|
||
Finally, Projection unifies with <selectors>/<projection>, where:
|
||
•<selectors>is the list of requested selectors.
|
||
•<projection>is a list of values coming from a single row from the
|
||
given table that matches the conditions. This mean this predicate
|
||
should be able to backtrack to generate all projections that match
|
||
the query.
|
||
For example, selec(persons,[+id,+first],[],P) returns as first result
|
||
P = [+id, +first]/[0, "Jeffrey"].
|
||
To obtain all projections that match the query, one could use the Prolog
|
||
query findall(X, selec(Table, Selectors, Conds, X), Projections).
|
||
For example: findall(X, selec(persons,[+last],[],X), Projections)
|
||
returns Projections = [[+last]/["Bowman"],[+last]/["Michaels"],. . . ]
|
||
Or if you only want the rows (since the selectors are repeated):
|
||
findall(X, selec(Table, Selectors, Conds, /X), Projections).
|
||
For example:
|
||
findall(Values, selec(persons,[+id,+first],[],Values), Projections)
|
||
returns: Projections = [[0, "Jeffrey"], [1, "Lorena"], [2, "Joseph"], ...
|
||
*/
|
||
selec(Table, _, _, _) :-
|
||
\+ tabl(Table, _),
|
||
throw("Table doesn't exist"),
|
||
!.
|
||
|
||
selec(Table, _, _, _) :-
|
||
tabl(Table, Cols),
|
||
length(Cols, L),
|
||
\+ current_predicate(Table/L),
|
||
!.
|
||
|
||
selec(Table, Selector, Cond, Projection) :-
|
||
tabl(Table, Cols),
|
||
row(Table, R),
|
||
does_match(Cond, Table, R),
|
||
selector(R,Cols, Selector, [],[],ColumnNames,ColumnValue),
|
||
reverse(ColumnNames, Names),
|
||
reverse(ColumnValue, Values),
|
||
|
||
Projection = Names/Values.
|
||
|
||
selec(Table, *, Cond, Projection) :-
|
||
tabl(Table, Cols),
|
||
row(Table, R),
|
||
does_match(Cond, Table, R),
|
||
Projection = Cols/R.
|
||
|
||
/**
|
||
* Row: A row from the table
|
||
* Cols: name of the column of the table
|
||
* [Selector|Selectors] name(s) of the column(s) for selection
|
||
* AccCols: acumulator list for the column(s) name(s)
|
||
* AccVals: acumulator list for the row(s) values
|
||
* ColumnNames: List returning the column(s) name(s)
|
||
* ColumnValue: List returning the row values for each column
|
||
*
|
||
* This predicate return
|
||
*/
|
||
|
||
selector(_,_,[],ColumnNames, ColumnValue, ColumnNames, ColumnValue):-!.
|
||
|
||
selector(Row, Cols, [+Selector|Selectors], AccCols, AccVals, ColumnNames, ColumnValue):-
|
||
(member(Selector, Cols) ->
|
||
%find the value corresponding to the column in the row
|
||
nth0(Index, Cols, Selector),
|
||
nth0(Index, Row, Value),
|
||
%go to next column tho evaluate
|
||
selector(Row, Cols, Selectors, [Selector|AccCols], [Value|AccVals], ColumnNames, ColumnValue)
|
||
;
|
||
string_concat(Selector, " isn't a column of table ", Error),
|
||
throw(Error)
|
||
).
|
||
|
||
:- dynamic selec/3.
|
||
/*
|
||
Simplified variant of the selec/4 predicate when there are no conditions
|
||
to be checked.
|
||
*/
|
||
selec(TableOrTables, Selectors, Projection) :-
|
||
tabl(TableOrTables, Cols),
|
||
row(TableOrTables, R),
|
||
selector(R,Cols, Selectors, [],[],ColumnNames,ColumnValue),
|
||
reverse(ColumnNames, Names),
|
||
reverse(ColumnValue, Values),
|
||
|
||
Projection = Names/Values.
|
||
|
||
selec(Table, *, Projection) :-
|
||
tabl(Table, Cols),
|
||
row(Table, R),
|
||
Projection = Cols/R.
|
||
|
||
:- dynamic query/2.
|
||
/*
|
||
where Query is a string whose syntax is defined by the following grammar:
|
||
⟨query⟩ ::= ⟨select⟩ | ⟨insert ⟩
|
||
⟨select⟩ ::= SELECT ⟨selectors⟩ FROM ⟨table⟩ [⟨where⟩];
|
||
⟨selectors⟩ ::= * | ⟨cols⟩
|
||
⟨cols⟩ ::= ⟨col ⟩ [, ⟨cols⟩]
|
||
⟨where⟩ ::= WHERE ⟨cond ⟩
|
||
⟨insert⟩ ::= INSERT INTO ⟨table⟩ [(⟨cols⟩)] VALUES (⟨values⟩);
|
||
⟨values⟩ ::= ⟨value⟩ [, ⟨values⟩]
|
||
About the notation: the pipe (|) and square brackets ([]) symbols in the
|
||
production rules above denote choice and optionality, respectively.
|
||
<table>and <col>denote table and column names (respectively). Ta-
|
||
ble names are atoms, while column names should follow the format out-
|
||
lined before (+<atom>). <cond>denotes a Prolog goal, in Prolog syntax
|
||
— following the same format as the possible values of items of the Conds
|
||
list passed to selec and delete.
|
||
<value>denotes a prolog value that can be stored into a row. You do
|
||
not need to handle parsing Prolog (<cond>and <value>) by yourself,
|
||
we’ll show how to do it below.
|
||
The semantics of this predicate is that of the SQL-predicate contained in
|
||
the Query string.
|
||
A “SELECT” query maps to the selec predicate, while an “INSERT”
|
||
query maps to the insert predicate. For “SELECT”, all instances of
|
||
<cond>(appearing in <where>) are mapped to the Conds parameter.
|
||
<selectors>map to the Selectors parameter. Tables names (or *) are
|
||
mapped to the Table parameter.
|
||
For “INSERT”, if the <cols>part is absent, the mapping to insert/2
|
||
is straightforward. If <cols>is present, you will have to:
|
||
1. reorder the values according to the columns that are present;
|
||
2. fill in the missing columns (if any) with a null default values.
|
||
|
||
Example:
|
||
query("INSERT INTO cities (+name, +state)
|
||
VALUES (\"Tempa\", \"Florida\");").
|
||
maps to (for instance):
|
||
insert(cities, ["Tempa", "Florida"]).
|
||
When used with “SELECT”:
|
||
•query/1 must display the results a bit like the rows predicate would.
|
||
You have some flexibility here (displaying the selected column names
|
||
is a nice touch for instance).
|
||
•query/2 must pass the Result parameter as last parameter to the
|
||
selec/4 predicate.
|
||
For “INSERT”, nothing needs to be printed by query/1 or in case of
|
||
success, and if query/2 is used the Result parameter can be ignored.
|
||
*/
|
||
query(Query, Result):-
|
||
parse_query(Query, Fun, Args),
|
||
apply(Fun, Args),
|
||
last(Args, Result).
|
||
|
||
/**
|
||
* Parse SELECT * FROM <table> WHERE <conds>
|
||
*/
|
||
parse_query(Query, selec, [AtomTable, *, AtomsConds, R]):-
|
||
split_string(Query, " ", ",;+", SplitQuery),
|
||
append(["SELECT"], L1, SplitQuery),
|
||
append(["*"], ["FROM"|L2], L1),
|
||
append([Table], ["WHERE"|Conds],L2),
|
||
atom_string(AtomTable, Table),
|
||
maplist(parse_cond, AtomsConds, Conds),!.
|
||
/**
|
||
* Parse SELECT <cols> FROM <table> WHERE <conds>
|
||
*/
|
||
parse_query(Query, selec, [AtomTable, AtomsSelectors, AtomsConds, R]):-
|
||
split_string(Query, " ", ",;+", SplitQuery),
|
||
append(["SELECT"], L1, SplitQuery),
|
||
append(Selectors, ["FROM"|L2], L1),
|
||
append([Table], ["WHERE"|Conds],L2),
|
||
atom_string(AtomTable, Table),
|
||
maplist(atom_string, AtomsSelectors1, Selectors),
|
||
maplist(add_plus, AtomsSelectors, AtomsSelectors1),
|
||
maplist(parse_cond, AtomsConds, Conds),!.
|
||
|
||
/**
|
||
* Parse SELECT * FROM <table>
|
||
*/
|
||
parse_query(Query, selec, [AtomTable, *, R]):-
|
||
split_string(Query, " ", ",;+", SplitQuery),
|
||
append(["SELECT"], L1, SplitQuery),
|
||
append(["*"], ["FROM",Table], L1),
|
||
atom_string(AtomTable, Table),
|
||
maplist(parse_cond, AtomsConds, Conds),!.
|
||
|
||
/**
|
||
* Parse SELECT <cols> FROM <table>
|
||
*/
|
||
parse_query(Query, selec, [AtomTable, AtomsSelectors, R]):-
|
||
split_string(Query, " ", ",;+", SplitQuery),
|
||
append(["SELECT"], L1, SplitQuery),
|
||
append(Selectors, ["FROM",Table], L1),
|
||
atom_string(AtomTable, Table),
|
||
maplist(atom_string, AtomsSelectors1, Selectors),
|
||
maplist(add_plus, AtomsSelectors, AtomsSelectors1).
|
||
|
||
parse_query(Query, insert, [AtomTable, Values]):-
|
||
split_string(Query, " ", '",();', SplitQuery),
|
||
append(["INSERT", "INTO"], L1, SplitQuery),
|
||
L1 = [Table | L2],
|
||
append(_, ["VALUES"|Values1], L2),
|
||
maplist(is_number, Values, Values1),
|
||
atom_string(AtomTable, Table).
|
||
|
||
add_plus(+A, A).
|
||
|
||
% parse >= conditon
|
||
parse_cond(+AtomLeft>=AtomRight, Cond):-
|
||
string_chars(Cond, Code),
|
||
append(LeftMemb, [>,=|RightMemb], Code),
|
||
string_chars(Left, LeftMemb),
|
||
string_chars(Right1, RightMemb),
|
||
split_string(Right1,"",'"',[Right]),
|
||
atom_string(AtomLeft, Left),
|
||
is_number(AtomRight, Right),!.
|
||
|
||
% parse <= conditon
|
||
parse_cond(+AtomLeft<=AtomRight, Cond):-
|
||
string_chars(Cond, Code),
|
||
append(LeftMemb, [<,=|RightMemb], Code),
|
||
string_chars(Left, LeftMemb),
|
||
string_chars(Right1, RightMemb),
|
||
split_string(Right1,"",'"',[Right]),
|
||
atom_string(AtomLeft, Left),
|
||
is_number(AtomRight, Right),!.
|
||
|
||
% parse < conditon
|
||
parse_cond(+AtomLeft<AtomRight, Cond):-
|
||
string_chars(Cond, Code),
|
||
append(LeftMemb, [<|RightMemb], Code),
|
||
string_chars(Left, LeftMemb),
|
||
string_chars(Right1, RightMemb),
|
||
split_string(Right1,"",'"',[Right]),
|
||
atom_string(AtomLeft, Left),
|
||
is_number(AtomRight, Right),!.
|
||
|
||
% parse > conditon
|
||
parse_cond(+AtomLeft>AtomRight, Cond):-
|
||
string_chars(Cond, Code),
|
||
append(LeftMemb, [>|RightMemb], Code),
|
||
string_chars(Left, LeftMemb),
|
||
string_chars(Right1, RightMemb),
|
||
split_string(Right1,"",'"',[Right]),
|
||
atom_string(AtomLeft, Left),
|
||
is_number(AtomRight, Right),!.
|
||
|
||
% parse = conditon
|
||
parse_cond( +AtomLeft=AtomRight, Cond):-
|
||
string_chars(Cond, Code),
|
||
append(LeftMemb, [=|RightMemb], Code),
|
||
string_chars(Left, LeftMemb),
|
||
string_chars(Right1, RightMemb),
|
||
split_string(Right1,"",'"',[Right]),
|
||
atom_string(AtomLeft, Left),
|
||
is_number(AtomRight, Right),!.
|
||
|
||
is_number(R, N):-
|
||
atom_number(N, R); R = N.
|
||
|
||
|
||
:- dynamic query/1.
|
||
/*
|
||
cf. query/2
|
||
*/
|
||
|
||
query(Query) :-
|
||
query(Query, R),
|
||
writeln(R),
|
||
fail.
|
||
query(_). |