diff --git a/Optimizer/calloptimizer.pl b/Optimizer/calloptimizer.pl index b7b5d257b..b669fe74f 100755 --- a/Optimizer/calloptimizer.pl +++ b/Optimizer/calloptimizer.pl @@ -1119,7 +1119,87 @@ /* -6 Shortcuts and Aliases +6 Normalizing SQL Identifiers Typed at the Prolog Toplevel + +The interactive SecondoPL shell is the plain Prolog toplevel, so a capitalized +identifier within an SQL query is read as a Prolog variable and not as the name +of a relation or an attribute. A query such as + +---- sql select count(*) from Trains where Trip passes mehringdamm. +---- + +therefore used to reach the optimizer with an unbound relation instead of +~trains~. + +The other interfaces (the TTY, the client/server protocol and the JavaGUI) all +down-case the first character of every identifier before the query text is +handed to the optimizer. Because the toplevel reader has already turned the +identifiers into variables, we do the same one step later: the toplevel hook +~expand\_query/4~ reports the variable names, and every named variable +occurring in the SQL part of the goal is bound to the atom the user meant. + +Only the argument holding the SQL query is treated this way, so output +arguments such as the plan and the cost of ~optimize/3~, the object name of a +~let~ and the executable rest of ~sql/2~ keep their spelling. Variables whose +name starts with an underscore are not reported by the reader and hence stay +free -- that is the escape hatch for an SQL argument that really should contain +a Prolog variable. + +*/ + +% Position of the SQL query within the goals that can be typed at the toplevel. +sqlGoalArg(sql, 1, 1). +sqlGoalArg(sql, 2, 1). +sqlGoalArg(optimize, 1, 1). +sqlGoalArg(optimize, 3, 1). +sqlGoalArg(streamOptimize, 3, 1). +sqlGoalArg(mOptimize, 3, 1). +sqlGoalArg(mStreamOptimize, 3, 1). +sqlGoalArg(let, 2, 2). +sqlGoalArg(let, 3, 2). + +% Down-case the first character only, exactly as the C++ interfaces do. +downcaseFirstChar(Name, Ident) :- + atom_chars(Name, [First | Rest]), + downcase_atom(First, Lower), + atom_chars(Ident, [Lower | Rest]). + +identicalMember(X, [Y | _]) :- + X == Y, !. + +identicalMember(X, [_ | Ys]) :- + identicalMember(X, Ys). + +/* +Bind those toplevel variables that occur in the SQL part of the goal. The +bindings of all other variables are passed on, so that the toplevel still +reports them. + +*/ + +bindSqlIdentifiers([], _, []). + +bindSqlIdentifiers([Name = Var | Bindings], SqlVars, Rest) :- + var(Var), + identicalMember(Var, SqlVars), !, + downcaseFirstChar(Name, Ident), + Var = Ident, + bindSqlIdentifiers(Bindings, SqlVars, Rest). + +bindSqlIdentifiers([Binding | Bindings], SqlVars, [Binding | Rest]) :- + bindSqlIdentifiers(Bindings, SqlVars, Rest). + +user:expand_query(Goal, Goal, Bindings, Rest) :- + Bindings \= [], + functor(Goal, Functor, Arity), + sqlGoalArg(Functor, Arity, ArgNo), + arg(ArgNo, Goal, Sql), + term_variables(Sql, SqlVars), + SqlVars \= [], + bindSqlIdentifiers(Bindings, SqlVars, Rest). + +/* +7 Shortcuts and Aliases */ @@ -1134,7 +1214,7 @@ /* -7 Testing +8 Testing */ diff --git a/Optimizer/distributed.pl b/Optimizer/distributed.pl index 297c56b56..262620c37 100644 --- a/Optimizer/distributed.pl +++ b/Optimizer/distributed.pl @@ -1358,8 +1358,14 @@ relation is not listed in SEC2DISTRIBUTED the unchanged name is returned in Variable ~ORel~ -*/ +The first clause requires a bound argument. Without that test an unbound +~DRel~ unifies with the pattern of the clause head again and again, so that a +relation name the caller could not resolve ends in a stack overflow instead of +in the ``Unknown relation'' message of ~lookupRel/2~. + +*/ removeDistributedSuffix(DRel as _, ORel) :- + nonvar(DRel), removeDistributedSuffix(DRel, ORel),!. removeDistributedSuffix(DRel, ORel) :-