diff --git a/gluon/dal/adapters/base.py b/gluon/dal/adapters/base.py index 38fcf725..b003e493 100644 --- a/gluon/dal/adapters/base.py +++ b/gluon/dal/adapters/base.py @@ -64,7 +64,7 @@ class BaseAdapter(ConnectionPool): __metaclass__ = AdapterMeta - native_json = False + driver_auto_json = False driver = None driver_name = None drivers = () # list of drivers from which to pick @@ -1113,13 +1113,20 @@ class BaseAdapter(ConnectionPool): query = self.common_filter(query,tablenames_for_common_filters) sql_w = ' WHERE ' + self.expand(query) if query else '' + JOIN = ' CROSS JOIN ' + if inner_join and not left: - sql_t = ', '.join([self.table_alias(t) for t in iexcluded + \ - itables_to_merge.keys()]) + # Wrap table references with parenthesis (approach 1) + # sql_t = ', '.join([self.table_alias(t) for t in iexcluded + \ + # itables_to_merge.keys()]) + # sql_t = '(%s)' % sql_t + # or approach 2: Use 'JOIN' instead comma: + sql_t = JOIN.join([self.table_alias(t) + for t in iexcluded + itables_to_merge.keys()]) for t in ijoinon: sql_t += ' %s %s' % (icommand, t) elif not inner_join and left: - sql_t = ', '.join([self.table_alias(t) for t in excluded + \ + sql_t = JOIN.join([self.table_alias(t) for t in excluded + \ tables_to_merge.keys()]) if joint: sql_t += ' %s %s' % (command, @@ -1133,7 +1140,7 @@ class BaseAdapter(ConnectionPool): tables_in_joinon = set(joinont + ijoinont) tables_not_in_joinon = \ all_tables_in_query.difference(tables_in_joinon) - sql_t = ','.join([self.table_alias(t) for t in tables_not_in_joinon]) + sql_t = JOIN.join([self.table_alias(t) for t in tables_not_in_joinon]) for t in ijoinon: sql_t += ' %s %s' % (icommand, t) if joint: @@ -1386,7 +1393,8 @@ class BaseAdapter(ConnectionPool): else: obj = str(obj) elif fieldtype == 'json': - if not self.native_json: + if not 'dumps' in self.driver_auto_json: + # always pass a string JSON string if have_serializers: obj = serializers.json(obj) elif simplejson: @@ -1524,7 +1532,7 @@ class BaseAdapter(ConnectionPool): return float(value) def parse_json(self, value, field_type): - if not self.native_json: + if not 'loads' in self.driver_auto_json: if not isinstance(value, basestring): raise RuntimeError('json data not a string') if isinstance(value, unicode): diff --git a/gluon/dal/adapters/google.py b/gluon/dal/adapters/google.py index f8652e4d..ea64ed87 100644 --- a/gluon/dal/adapters/google.py +++ b/gluon/dal/adapters/google.py @@ -5,7 +5,8 @@ import re from .._compat import pjoin from .._globals import IDENTITY, LOGGER, THREAD_LOCAL from .._load import classobj, gae, ndb, NDBDecimalProperty, GAEDecimalProperty, \ - namespace_manager, Key, NDBPolyModel, PolyModel, rdbms + namespace_manager, Key, NDBPolyModel, PolyModel, rdbms, have_serializers, \ + serializers, simplejson from ..objects import Table, Field, Expression, Query from ..helpers.classes import SQLCustomType, SQLALL, Reference, UseDatabaseStoredFile from ..helpers.methods import use_common_filters, xorify @@ -173,6 +174,17 @@ class GoogleDatastoreAdapter(NoSQLAdapter): def parse_id(self, value, field_type): return value + def represent(self, obj, fieldtype): + if fieldtype == "json": + if have_serializers: + return serializers.json(obj) + elif simplejson: + return simplejson.dumps(obj) + else: + raise Exception("Could not dump json object (missing json library)") + else: + return NoSQLAdapter.represent(self, obj, fieldtype) + def create_table(self,table,migrate=True,fake_migrate=False, polymodel=None): myfields = {} for field in table: diff --git a/gluon/dal/adapters/mongo.py b/gluon/dal/adapters/mongo.py index 0918f932..fd5aaeea 100644 --- a/gluon/dal/adapters/mongo.py +++ b/gluon/dal/adapters/mongo.py @@ -10,8 +10,8 @@ from ..helpers.methods import xorify from .base import NoSQLAdapter class MongoDBAdapter(NoSQLAdapter): - native_json = True drivers = ('pymongo',) + driver_auto_json = ['loads','dumps'] uploads_in_blob = False diff --git a/gluon/dal/adapters/postgre.py b/gluon/dal/adapters/postgre.py index 7045ff69..c675f39e 100644 --- a/gluon/dal/adapters/postgre.py +++ b/gluon/dal/adapters/postgre.py @@ -175,16 +175,21 @@ class PostgreSQLAdapter(BaseAdapter): # (to be added to after_connection) if self.driver_name == "pg8000": supports_json = self.connection.server_version >= "9.2.0" - elif (self.driver_name == "psycopg2") and \ - (self.driver.__version__ >= "2.0.12"): + elif (self.driver_name == "psycopg2" and + self.driver.__version__ >= "2.0.12"): supports_json = self.connection.server_version >= 90200 elif self.driver_name == "zxJDBC": supports_json = self.connection.dbversion >= "9.2.0" - else: supports_json = None + else: + supports_json = None if supports_json: self.types["json"] = "JSON" - self.native_json = True - else: LOGGER.debug("Your database version does not support the JSON data type (using TEXT instead)") + if (self.driver_name == "psycopg2" and + self.driver.__version__ >= '2.5.0'): + self.driver_auto_json = ['loads'] + else: + LOGGER.debug("Your database version does not support the JSON" + " data type (using TEXT instead)") def LIKE(self,first,second): args = (self.expand(first), self.expand(second,'string')) diff --git a/gluon/dal/helpers/methods.py b/gluon/dal/helpers/methods.py index 181336f2..f1ada034 100644 --- a/gluon/dal/helpers/methods.py +++ b/gluon/dal/helpers/methods.py @@ -192,7 +192,7 @@ def smart_query(fields,text): elif op == 'notbelongs': new_query = ~field.belongs(value.split(',')) elif field.type in ('text', 'string', 'json'): if op == 'contains': new_query = field.contains(value) - elif op == 'like': new_query = field.like(value) + elif op == 'like': new_query = field.ilike(value) elif op == 'startswith': new_query = field.startswith(value) elif op == 'endswith': new_query = field.endswith(value) else: raise RuntimeError("Invalid operation") @@ -246,7 +246,7 @@ def sqlhtml_validators(field): if field_type in (('string', 'text', 'password')): requires.append(validators.IS_LENGTH(field_length)) elif field_type == 'json': - requires.append(validators.IS_EMPTY_OR(validators.IS_JSON(native_json=field.db._adapter.native_json))) + requires.append(validators.IS_EMPTY_OR(validators.IS_JSON())) elif field_type == 'double' or field_type == 'float': requires.append(validators.IS_FLOAT_IN_RANGE(-1e100, 1e100)) elif field_type == 'integer': diff --git a/gluon/dal/objects.py b/gluon/dal/objects.py index dae5a614..c0cd0001 100644 --- a/gluon/dal/objects.py +++ b/gluon/dal/objects.py @@ -1207,6 +1207,9 @@ class Expression(object): op = case_sensitive and db._adapter.LIKE or db._adapter.ILIKE return Query(db, op, self, value) + def ilike(self, value): + return self.like(case_sensitive=False) + def regexp(self, value): db = self.db return Query(db, db._adapter.REGEXP, self, value) @@ -1254,14 +1257,12 @@ class Expression(object): def contains(self, value, all=False, case_sensitive=False): """ - The case_sensitive parameters is only useful for PostgreSQL - For other RDMBs it is ignored and contains is always case insensitive For MongoDB and GAE contains is always case sensitive """ db = self.db if isinstance(value,(list, tuple)): - subqueries = [self.contains(str(v).strip(),case_sensitive=case_sensitive) - for v in value if str(v).strip()] + subqueries = [self.contains(str(v),case_sensitive=case_sensitive) + for v in value if str(v)] if not subqueries: return self.contains('') else: