roxen.lists.roxen.general

Subject Author Date
Lazy evaluation of entities is not so lazy? Stephen R. van den Berg <srb[at]cuci[dot]nl> 26-02-2009
I tried my hand at a new module which basically allows one to use
&lang.somemnemonic; kinds of entities which substitute from a database
automatically.

The part of the database actually referenced by the entities in the page
is pulled in on the fly.
In the docs it says that class Value types allow lazy evaluation.
In practice I see that rather consistently, the index operator is called
first, and directly followed by the evaluation operator which forces me
to actually query the database immediately.

It would have been better (obviously) if all the index operators of all
the used entities in the lang scope would have been called first, and
only *then* the evaluations would be done in one fell swoop.

Am I doing something wrong which fails to trigger the lazy evaluation?
Or is this something that cannot be triggered/forced in the current code?

The module source is provided below for reference.  The documentation is still
wrong/lacking, the module is still being debugged.

commit 80b316d1031a4bf773fef66f29f875a7cb0a0dcb
Author: Stephen R. van den Berg <<srb[at]cuci.nl>>
Date:   Thu Feb 26 00:10:37 2009 +0100

    New module sqlanguage for multilingual entities.

diff --git a/server/modules/misc/sqlanguage.pike
b/server/modules/misc/sqlanguage.pike
new file mode 100644
index 0000000..1d1df4e
--- /dev/null
+++ b/server/modules/misc/sqlanguage.pike
@@ -0,0 +1,297 @@
+// This is a roxen module which provides SQL assisted multilanguage support.
+//
+// Copyright (c) 2004-2009, Stephen R. van den Berg, The Netherlands.
+//                     <<srb[at]cuci.nl>>
+//
+// This module is open source software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; either version 2, or (at your option) any
+// later version.
+//
+
+//<locale-token project="mod_sqlanguage">_</locale-token>
+#define _(X,Y)  _DEF_LOCALE("mod_sqlanguage",X,Y)
+
+constant thread_safe = 1;
+constant language = roxen->language;
+
+#include <module.h>
+#include <config.h>
+
+inherit "module";
+
+// ---------------- Module registration stuff ----------------
+
+constant module_type = MODULE_TAG | MODULE_PROVIDER;
+LocaleString module_name = _(1,"Misc: SQL multilanguage");
+LocaleString module_doc  = _(2,
+ "This module provides SQL assisted multilanguage support.<br />"
+ "<p>Copyright &copy; 2004-2009, by "
+ "<a href='mailto:<srb[at]cuci.nl>'>Stephen R. van den Berg</a>, "
+ "The Netherlands.</p>"
+ "<p>This module is open source software; you can redistribute it and/or "
+ "modify it under the terms of the GNU General Public License as published "
+ "by the Free Software Foundation; either version 2, or (at your option) any "
+ "later version.</p>");
+
+constant name = "sqlanguage";
+
+private int usecache;
+
+private string cachemap;
+
+void create()
+{
+  set_module_creator("Stephen R. van den Berg <<srb[at]cuci.nl>>");
+  defvar( "db",
+   DatabaseVar( "sql_users",({}),0,
+    _(3,"Database"),
+    _(4,"This is the database that this module will store its users in.")));
+}
+
+private Sql.Sql getdb()
+{ return DBManager.get(query("db"), my_configuration());
+}
+
+private int getglobals(string varname)
+{ array r=getdb()->query("SELECT vint FROM globals WHERE gname=:gname LIMIT 1",
+   ([":gname":varname]));
+  return r && sizeof(r) && (int) r[0]->vint;
+}
+
+private void initvars()
+{ usecache = getglobals("usecache");
+}
+
+static mapping(string:int) replacecounts=([]);
+
+string status() {
+  string s="<tr><td colspan=2>None yet</td></tr>";
+  if(sizeof(replacecounts))
+  { s="";
+    foreach(sort(indices(replacecounts)),string scope)
+      s+=sprintf("<tr><td>%s</td><td align=right>%d</td></tr>",
+       scope,replacecounts[scope]);
+  }
+  return _(5,"<table border=1><tr><th>Scope</th><th>Substitutions</th></tr>"+
+   s+"</table>");
+}
+
+void start()
+{ string db=query("db");
+  cachemap=name+"_"+query("db");
+  query_tag_set()->prepare_context=set_entities;
+  initvars();
+}
+
+string query_provides() {
+  return "modified";
+}
+
+// ----------------- Entities ----------------------
+
+private int userlanguage = 0;
+
+#define USE_rxml_const_eval 1
+
+private class langentity
+{ inherit RXML.Value;
+
+  string value;
+
+#ifdef USE_rxml_const_eval
+  string rxml_const_eval(RXML.Context c, string var, string scope_name)
+#else
+  mixed rxml_var_eval(RXML.Context c, string var, string scope_name,
+   void|RXML.Type type)
+#endif
+  { if(!value)
+#if 0 // TODO: lazy evaluation of entities does not seem to be supported (yet?)
+    { mapping langmap = c->id->misc->langscope;
+      multiset ind = (<>);
+      string cmap = sprintf("%s.%d", cachemap, userlanguage);
+      foreach(langmap; string name; langentity ce)
+        if(!ce->value && !(ce->value = cache_lookup(cmap, name)))
+	  ind[name] = 1;
+      if(sizeof(ind))
+      { array r = getdb()->query(sprintf("SELECT du.gname, "
+          " COALESCE(oth.txt,du.txt) AS txt "
+          "FROM globaltxt AS du LEFT JOIN globaltxt AS oth "
+          "  ON du.gname=oth.gname AND oth.language='%d' "
+          "WHERE du.language=0 "
+          " AND du.gname IN ('%s') ",userlanguage,indices(ind)*"','"));
+        mapping row;
+        foreach(r ;; row)
+        { string name = row->gname;
+          ind -= (<name>);
+          cache_set(cmap, name, langmap[name]->value = row->txt, usecache);
+        }
+        foreach(ind;string name;)
+	  langmap[name]->value = sprintf("[[%s]]", name);
+      }
+    }
+#else
+    { string cmap = sprintf("%s.%d", cachemap, userlanguage);
+      if(!(value = cache_lookup(cmap, var)))
+      { array r = getdb()->query("SELECT "
+          " COALESCE(oth.txt,du.txt) AS txt "
+          "FROM globaltxt AS du LEFT JOIN globaltxt AS oth "
+          "  ON du.gname=oth.gname AND oth.language=:language "
+          "WHERE du.language=0 "
+          " AND du.gname=:gname "
+          "LIMIT 1",([":language":userlanguage,":gname":var]));
+        if(sizeof(r))
+          cache_set(cmap, var, value = r[0]->txt, usecache);
+	else
+	  value = sprintf("[[%s]]", var);
+      }
+    }
+#endif
+    return value;
+  }
+}
+
+private class langscope
+{ inherit RXML.Scope;
+
+  mixed `[] (string var, void|RXML.Context c, void|string scope,
+   void|RXML.Type type)
+  { mapping m = (c = c || RXML_CONTEXT)->id->misc->langscope;
+    if(!m)
+      m = c->id->misc->langscope = ([]);
+    mixed res = m[var];
+    if(!res)
+      m[var] = res = langentity();
+    return res;
+  }
+
+#ifdef USE_rxml_const_eval
+  mixed `[]= (string var, mixed val, void|RXML.Context ctx,
+   void|string scope_name)
+  { // no-op just provided to support rxml_const_eval()
+    return val;
+  }
+#endif
+
+  array(string) _indices(void|RXML.Context c)
+  { mapping m =(c || RXML_CONTEXT)->id->misc->langscope;
+    return m ? indices(m) : ({});
+  }
+
+  array(string) _values(void|RXML.Context c)
+  { mapping m =(c || RXML_CONTEXT)->id->misc->langscope;
+    return m ? values(m) : ({});
+  }
+
+  string _sprintf (int flag)
+  { return flag == 'O' && "RXML.Scope(lang)";
+  }
+}
+
+void set_entities(RXML.Context c) {
+  c->add_scope("lang", langscope());
+}
+
+// ------------------- Containers ----------------
+
+
+class DatabaseVar
+{
+  inherit Variable.StringChoice;
+  array get_choice_list( )
+  {
+    return sort(DBManager.list( my_configuration() ));
+  }
+}
+
+// --------------------- Documentation -----------------------
+
+TAGDOCUMENTATION;
+#ifdef manual
+constant tagdoc=([
+"expand-variables":#"<desc type='cont'><p><short hide='hide'>
+ Expand certain scopes only.</short>The &lt;expand-variables&gt;
+ tag allows one to specify which scopes to parse and expand, while
+ leaving all other entities and tags untouched.</p>
+<p>
+ The tag is intended for situations where external data needs to be parsed
+ for certain entities only, not affecting RXML code nor other
+ entities/variables.
+</p></desc>
+
+<attr name='scope' value='list'><p>
+ Comma-separated array of scopes to expand.  If omitted, all known scopes
+ are parsed and replaced.</p>
+</attr>
+
+<attr name='from'><p>
+ Variable to use as source instead of the content of the container.</p>
+</attr>
+",
+    ]);
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// ------------------- Tags ------------------------
+
+#if 0
+class TagInsertFile {
+  inherit RXML.Tag;
+  constant name = "insert";
+  constant plugin_name = "file";
+
+  RXML.Type get_type(mapping args) {
+    if (args->quote=="html")
+      return RXML.t_text;
+    return RXML.t_xml;
+  }
+
+  string get_data(string var, mapping args, RequestID id) {
+    string result;
+    if(args->nocache) {
+      int nocache=id->pragma["no-cache"];
+      id->pragma["no-cache"] = 1;
+      result=id->conf->try_get_file(var, id);
+      id->pragma["no-cache"] = nocache;
+    }
+    else result = id->conf->try_get_file(var, id);
+    if(!result) RXML.run_error("No such file ("+var+").\n");
+
+#ifdef OLD_RXML_COMPAT
+    if(id->conf->old_rxml_compat)
+      return Roxen.parse_rxml(result, id);
+#endif
+    return result;
+  }
+}
+#endif
+
+// ------------------- Containers ----------------
+
+// ----------------- If registration stuff --------------
+
+// ---------------- Emit registration stuff --------------
+
+// ---------------- API registration stuff ---------------
+
+
+
-- 
Sincerely,
           Stephen R. van den Berg.

Every minute you worry about the past, eats another minute out of your future.