linfo2335-programming-parad.../project-1/solution.pl

500 lines
15 KiB
Perl
Raw Normal View History

/**
* Authors:
* Dubois Brieuc
* Dubois Simon
2024-03-15 23:19:57 +01:00
*
* Known Limitation and problems:
* Currently a condtion in a query as to contain no space in order to function properly
* Ex: the condtion +name="Dakota" will work but the conditions +name = "Dakota"
* or +name="North Dakota" will not. This is due to the way we parse the query. We did it
* with split_string. We were not able to use the prolog_term predicate given to us.
*
* SQL test:
* Test 10 failed because instead of returning [+<atom>, +<atom>] for the column name
* we return [<atom>, <atom>] because that's how we store the column names after an insert
*
* Test 12 and 21:
* When we return row of an empty table instead of having [] we have [_] same thing happens after
* we drop a table.
*
* DCG test:
* Test 5 failed:
* We don't cover the case where the number of values inserted isn't the same as the number of columns
* in the table.
*/
2024-03-14 15:32:14 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic tables/0.
2024-03-01 14:03:54 +01:00
/*
Prints the names of all existing tables, one per line (using writeln/1).
2024-03-01 14:03:54 +01:00
A table name is always an atom.
*/
2024-03-02 00:16:47 +01:00
tables :- tabl(X, _), writeln(X).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic tables/1.
2024-03-01 14:03:54 +01:00
/*
Unify Tables with a list of the names of all existing tables.
*/
2024-03-02 00:16:47 +01:00
tables(Tables) :- findall(X, tabl(X, _), Tables).
2024-03-01 14:03:54 +01:00
2024-03-01 14:59:28 +01:00
:- dynamic create_table/2.
2024-03-01 14:03:54 +01:00
/*
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).
2024-03-01 14:03:54 +01:00
*/
2024-03-02 00:16:47 +01:00
create_table(Table, Cols) :-
(tabl(Table, _) -> throw("Table already exists"));
assertz(tabl(Table, Cols)).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic cols/2.
2024-03-01 14:03:54 +01:00
/*
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).
*/
2024-03-02 09:38:48 +01:00
cols(Table, Cols) :-
(tabl(Table, _) -> tabl(Table, Cols));
throw("Table doesn't exist").
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic row/2.
2024-03-01 14:03:54 +01:00
/*
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.
*/
2024-03-14 23:35:33 +01:00
row(Table, _) :-
\+ tabl(Table, _),
throw("Table doesn't exist"),
!.
row(Table, _) :-
tabl(Table, Cols),
length(Cols, L),
\+ current_predicate(Table/L),
!.
2024-03-13 09:49:08 +01:00
row(Table, Row) :-
2024-03-14 23:35:33 +01:00
tabl(Table, Cols),
length(Cols, L),
length(Row, L),
apply(Table, Row).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic rows/1.
2024-03-01 14:03:54 +01:00
/*
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
2024-03-01 14:03:54 +01:00
exception.
*/
2024-03-13 09:49:08 +01:00
rows(Table) :-
\+ tabl(Table, _),
throw(table_does_not_exist(Table)),
!.
2024-03-14 23:35:33 +01:00
rows(Table) :-
tabl(Table, Cols),
length(Cols, L),
\+ current_predicate(Table/L),
!.
2024-03-13 09:49:08 +01:00
rows(Table) :-
row(Table, Row),
writeln(Row),
fail.
rows(_).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic insert/2.
2024-03-01 14:03:54 +01:00
/*
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
2024-03-01 14:03:54 +01:00
exception.
If the row does not have as many elements as the number of columns in
the table, the predicate throws a descriptive exception.
2024-03-01 14:03:54 +01:00
*/
2024-03-10 13:14:13 +01:00
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")
).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic drop/1.
2024-03-01 14:03:54 +01:00
/*
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
2024-03-01 14:03:54 +01:00
exception.
*/
2024-03-10 14:19:45 +01:00
drop(Table) :-
(tabl(Table, _) ->
delete(Table), retract(tabl(Table, _));
throw("Table doesn't exist")
).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic delete/1.
2024-03-01 14:03:54 +01:00
/*
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
2024-03-01 14:03:54 +01:00
exception.
*/
2024-03-10 14:19:45 +01:00
delete(Table) :-
(tabl(Table, _) ->
tabl(Table, C), length(C, L), call(abolish, Table, L);
throw("Table doesn't exist")
).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic delete/2.
2024-03-01 14:03:54 +01:00
/*
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
2024-03-01 14:03:54 +01:00
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.
*/
2024-03-10 14:40:10 +01:00
delete(Table, Conds) :-
(tabl(Table, _) ->
2024-03-14 18:06:34 +01:00
row(Table, Row),
2024-03-14 18:11:33 +01:00
does_match(Conds, Table, Row),
Term =.. [Table | Row],
retract(Term),
fail
2024-03-14 18:06:34 +01:00
;
2024-03-10 14:40:10 +01:00
throw("Table doesn't exist")
2024-03-14 18:11:33 +01:00
).
delete(_, _).
2024-03-14 18:06:34 +01:00
2024-03-14 18:11:33 +01:00
does_match([], _, _) :- !.
2024-03-15 23:19:57 +01:00
/**
* fails if conditons isn't respected in row
*/
2024-03-14 18:06:34 +01:00
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).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic selec/4.
2024-03-01 14:03:54 +01:00
/*
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
2024-03-01 14:03:54 +01:00
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),
!.
2024-03-14 18:32:38 +01:00
selec(Table, Selector, Cond, Projection) :-
2024-03-15 23:19:57 +01:00
%fetch columns names and all rows of Table
2024-03-14 18:32:38 +01:00
tabl(Table, Cols),
row(Table, R),
2024-03-15 23:19:57 +01:00
%remove rows that don't respect the conditions
2024-03-14 18:32:38 +01:00
does_match(Cond, Table, R),
2024-03-15 23:19:57 +01:00
%reduce result to only selected columns
2024-03-14 18:32:38 +01:00
selector(R,Cols, Selector, [],[],ColumnNames,ColumnValue),
2024-03-15 23:19:57 +01:00
%reorder result to match query order
reverse(ColumnNames, Names),
reverse(ColumnValue, Values),
2024-03-15 23:19:57 +01:00
%unify the lists to projection
Projection = Names/Values.
2024-03-14 15:32:14 +01:00
selec(Table, *, Cond, Projection) :-
tabl(Table, Cols),
row(Table, R),
does_match(Cond, Table, R),
Projection = Cols/R.
2024-03-14 15:32:14 +01:00
/**
2024-03-15 23:19:57 +01:00
* +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
2024-03-14 15:32:14 +01:00
*/
2024-03-15 23:19:57 +01:00
%base case
selector(_,_,[],ColumnNames, ColumnValue, ColumnNames, ColumnValue):-!.
2024-03-14 18:32:38 +01:00
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),
2024-03-15 23:19:57 +01:00
%go to next column tho evaluate and store value and corresponging name
2024-03-14 18:32:38 +01:00
selector(Row, Cols, Selectors, [Selector|AccCols], [Value|AccVals], ColumnNames, ColumnValue)
;
2024-03-14 18:40:16 +01:00
string_concat(Selector, " isn't a column of table ", Error),
2024-03-14 18:32:38 +01:00
throw(Error)
2024-03-14 15:32:14 +01:00
).
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic selec/3.
2024-03-01 14:03:54 +01:00
/*
Simplified variant of the selec/4 predicate when there are no conditions
to be checked.
*/
2024-03-14 18:53:32 +01:00
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.
2024-03-01 14:03:54 +01:00
selec(Table, *, Projection) :-
tabl(Table, Cols),
row(Table, R),
Projection = Cols/R.
2024-03-01 14:07:09 +01:00
:- dynamic query/2.
2024-03-01 14:03:54 +01:00
/*
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.
2024-03-15 23:19:57 +01:00
<value>denotes a prolog value that can be stored into a row.
2024-03-01 14:03:54 +01:00
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.
*/
2024-04-23 14:59:23 +02:00
<<<<<<< HEAD:solution.pl
2024-03-15 19:55:26 +01:00
query(Query, Result):-
parse_query(Query, Fun, Args),
apply(Fun, Args),
last(Args, Result).
2024-03-15 23:19:57 +01:00
/**
* For all parse_query on Query must be initiated
*/
2024-03-15 19:55:26 +01:00
/**
* Parse SELECT * FROM <table> WHERE <conds>
*/
parse_query(Query, selec, [AtomTable, *, AtomsConds, R]):-
2024-03-15 23:19:57 +01:00
%split the query
2024-03-15 19:55:26 +01:00
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(["*"], ["FROM"|L2], L1),
append([Table], ["WHERE"|Conds],L2),
2024-03-15 23:19:57 +01:00
%cast into correct type
2024-03-15 19:55:26 +01:00
atom_string(AtomTable, Table),
maplist(parse_cond, AtomsConds, Conds),!.
/**
* Parse SELECT <cols> FROM <table> WHERE <conds>
*/
parse_query(Query, selec, [AtomTable, AtomsSelectors, AtomsConds, R]):-
2024-03-15 23:19:57 +01:00
%split the query
2024-03-15 19:55:26 +01:00
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(Selectors, ["FROM"|L2], L1),
append([Table], ["WHERE"|Conds],L2),
2024-03-15 23:19:57 +01:00
%cast into correct type
2024-03-15 19:55:26 +01:00
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]):-
2024-03-15 23:19:57 +01:00
%split the query
2024-03-15 19:55:26 +01:00
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(["*"], ["FROM",Table], L1),
2024-03-15 23:19:57 +01:00
%cast into correct type
2024-03-15 19:55:26 +01:00
atom_string(AtomTable, Table),
maplist(parse_cond, AtomsConds, Conds),!.
/**
* Parse SELECT <cols> FROM <table>
*/
parse_query(Query, selec, [AtomTable, AtomsSelectors, R]):-
2024-03-15 23:19:57 +01:00
%split the query
2024-03-15 19:55:26 +01:00
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(Selectors, ["FROM",Table], L1),
2024-03-15 23:19:57 +01:00
%cast into correct type
2024-03-15 19:55:26 +01:00
atom_string(AtomTable, Table),
maplist(atom_string, AtomsSelectors1, Selectors),
maplist(add_plus, AtomsSelectors, AtomsSelectors1).
parse_query(Query, insert, [AtomTable, Values]):-
2024-03-15 23:19:57 +01:00
%split the query
2024-03-15 19:55:26 +01:00
split_string(Query, " ", '",();', SplitQuery),
append(["INSERT", "INTO"], L1, SplitQuery),
L1 = [Table | L2],
append(_, ["VALUES"|Values1], L2),
2024-03-15 23:19:57 +01:00
%cast into correct type
maplist(is_number, Values, Values1),
2024-03-15 19:55:26 +01:00
atom_string(AtomTable, Table).
add_plus(+A, A).
2024-03-15 23:19:57 +01:00
/**
* For all parse_cond only Cond must be initiated
*/
2024-03-15 19:55:26 +01:00
% 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.
2024-04-23 14:59:23 +02:00
=======
query(Query, Result, Rest) :-
prolog_term(Result, [';']).
>>>>>>> d17a9b1 (Move project-1):project-1/solution.pl
2024-03-01 14:03:54 +01:00
2024-03-01 14:07:09 +01:00
:- dynamic query/1.
2024-03-01 14:03:54 +01:00
/*
cf. query/2
*/
2024-03-15 19:55:26 +01:00
query(Query) :-
query(Query, R),
writeln(R),
fail.
query(_).