docs: function(filter) {
              if (filter instanceof OpenLayers.Geometry) {
                  return filter.toString();
              }
              switch (filter.CLASS_NAME) {
                  case "OpenLayers.Filter.Spatial":
                      switch(filter.type) {
                          case OpenLayers.Filter.Spatial.BBOX:
                              return "BBOX(" +
                                  filter.property + "," +
                                  filter.value.toBBOX() +
                                  ")";
                          case OpenLayers.Filter.Spatial.DWITHIN:
                              return "DWITHIN(" +
                                  filter.property + ", " +
                                  this.write(filter.value) + ", " +
                                  filter.distance + ")";
                          case OpenLayers.Filter.Spatial.WITHIN:
                              return "WITHIN(" +
                                  filter.property + ", " +
                                  this.write(filter.value) + ")";
                          case OpenLayers.Filter.Spatial.INTERSECTS:
                              return "INTERSECTS(" +
                                  filter.property + ", " +
                                  this.write(filter.value) + ")";
                          case OpenLayers.Filter.Spatial.CONTAINS:
                              return "CONTAINS(" +
                                  filter.property + ", " +
                                  this.write(filter.value) + ")";
                          default:
                              throw new Error("Unknown spatial filter type: " + filter.type);
                      }
                  case "OpenLayers.Filter.Logical":
                      if (filter.type == OpenLayers.Filter.Logical.NOT) {
                          // TODO: deal with precedence of logical operators to 
                          // avoid extra parentheses (not urgent)
                          return "NOT (" + this.write(filter.filters[0]) + ")";
                      } else {
                          var res = "(";
                          var first = true;
                          for (var i = 0; i < filter.filters.length; i++) {
                              if (first) {
                                  first = false;
                              } else {
                                  res += ") " + logicalReverse[filter.type] + " (";
                              }
                              res += this.write(filter.filters[i]);
                          }
                          return res + ")";
                      }
                  case "OpenLayers.Filter.Comparison":
                      if (filter.type == OpenLayers.Filter.Comparison.BETWEEN) {
                          return filter.property + " BETWEEN " + 
                              this.write(filter.lowerBoundary) + " AND " + 
                              this.write(filter.upperBoundary);
                      } else {
                          return (filter.value !== null) ? filter.property +
                              " " + operatorReverse[filter.type] + " " + 
                              this.write(filter.value) : filter.property +
                              " " + operatorReverse[filter.type];
                      }
                  case undefined:
                      if (typeof filter === "string") {
                          return "'" + filter.replace(/'/g, "''") + "'";
                      } else if (typeof filter === "number") {
                          return String(filter);
                      }
                  default:
                      throw new Error("Can't encode: " + filter.CLASS_NAME + " " + filter);
              }
          }