/** * 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 +where 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 /, where: •is the list of requested selectors. •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. and denote table and column names (respectively). Ta- ble names are atoms, while column names should follow the format out- lined before (+). 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. denotes a prolog value that can be stored into a row. You do not need to handle parsing Prolog (and ) 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 (appearing in ) are mapped to the Conds parameter. map to the Selectors parameter. Tables names (or *) are mapped to the Table parameter. For “INSERT”, if the part is absent, the mapping to insert/2 is straightforward. If 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
WHERE */ 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 FROM
WHERE */ 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
*/ 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 FROM
*/ 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 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(_).