:- use_module(library(dcg/basics)). % DCG rule that checks if the remainder of the input % starts with Delim (a list), but does not consume % any input. lookahead(Delim , L, L) :- prefix(Delim , L). % Checks if the remainder of the input starts with one % of the items in the supplied list (as per lookahead /3). lookaheads([H|T]) --> lookahead(H), ! ; lookaheads(T). % Reads text until one of the delimiters in Delims is % encountered , then unifies R with the Prolog term parsed % from the text. % ! Will crash the parse if a delimiter is encountered % but the intervening text is not a Prolog term (you % don’t have this handle this case). prolog_term(R, Delims) --> string(S), lookaheads(Delims), !, { read_term_from_atom(S, R, []) }. :- dynamic tables/0. /* Prints the names of all existing tables, one per line (use 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 must throw a descriptive exception (use throw/1). All exceptions must have a descriptive error message. */ 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, Row) :- tabl(Table, Cols) -> ( length(Cols, L), length(Row, L), apply(Table, Row) ); fail. :- dynamic rows/1. /* Displays all rows in the given table, one per line (use writeln/1). If the given table does not exist, the predicate must throw a descriptive exception. */ rows(Table) :- \+ tabl(Table, _), throw(table_does_not_exist(Table)), !. 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 must throw a descriptive exception. If the row does not have as many elements as the number of columns in the table, the predicate must throw 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. Do make sure that all of its rows are deleted as well, so that they don’t magically reappear again if you would recreate a table with the same name and signature later on. If the given table does not exist, the predicate must throw 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 must throw 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 must still exist after. If the given table does not exist, the predicate must throw 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. (See tests.pl for some concrete usage examples.) */ delete(Table, Conds) :- (tabl(Table, _) -> row(Table, Row), does_match(Conds, Table, Row) ; throw("Table doesn't exist") ). does_match([], _, Row) :- !. does_match([Cond|Rest], Table, Row) :- % Extract the operator, field and value from the condition Cond =.. [Operator | T], [+F, S] = T, tabl(Table, Cols), member(F, Cols), nth0(Index, Cols, F), nth0(Index, Row, Value), apply(Operator, [Value, S]), does_match(Rest, Table, Row). :- dynamic selec/4. /* Note that the name of this predicate is selec (without t) for the simple reason that select/4 is already a built-in Prolog predicate. 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. (See above for what selectors look like.) 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, Selector, Cond, Projection) :- tabl(Table, Cols), row(Table, R), does_match(Cond, Table, R), selector(R,Cols, Selector, [],[],ColumnNames,ColumnValue), Projection = ColumnNames/ColumnValue. %base case selector(_,_,[],ColumnNames, ColumnValue, ColumnNames, ColumnValue):-!. /** * 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 */ 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 ", X), string_concat(X, 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). */ :- 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). */ :- dynamic query/1. /* cf. query/2 */ /* query(Query). */