summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/ansisql.py15
-rw-r--r--lib/sqlalchemy/databases/mysql.py11
-rw-r--r--lib/sqlalchemy/databases/oracle.py22
-rw-r--r--lib/sqlalchemy/databases/postgres.py10
-rw-r--r--lib/sqlalchemy/databases/sqlite.py10
-rw-r--r--lib/sqlalchemy/mapping/properties.py30
-rw-r--r--lib/sqlalchemy/sql.py6
-rw-r--r--lib/sqlalchemy/util.py2
8 files changed, 95 insertions, 11 deletions
diff --git a/lib/sqlalchemy/ansisql.py b/lib/sqlalchemy/ansisql.py
index 691106a71..79215dec7 100644
--- a/lib/sqlalchemy/ansisql.py
+++ b/lib/sqlalchemy/ansisql.py
@@ -269,7 +269,12 @@ class ANSICompiler(sql.Compiled):
t = self.get_str(select.having)
if t:
text += " \nHAVING " + t
-
+
+ if select.limit is not None or select.offset is not None:
+ # TODO: ok, so this is a simple limit/offset thing.
+ # need to make this DB neutral for mysql, oracle
+ text += self.limit_clause(select)
+
if getattr(select, 'issubquery', False):
self.strings[select] = "(" + text + ")"
else:
@@ -277,6 +282,14 @@ class ANSICompiler(sql.Compiled):
self.froms[select] = "(" + text + ")"
+ def limit_clause(self, select):
+ if select.limit is not None:
+ return " \n LIMIT " + str(select.limit)
+ if select.offset is not None:
+ if select.limit is None:
+ return " \n LIMIT -1"
+ return " OFFSET " + str(select.offset)
+
def visit_table(self, table):
self.froms[table] = table.fullname
self.strings[table] = ""
diff --git a/lib/sqlalchemy/databases/mysql.py b/lib/sqlalchemy/databases/mysql.py
index 9e3f677d1..1eef6facb 100644
--- a/lib/sqlalchemy/databases/mysql.py
+++ b/lib/sqlalchemy/databases/mysql.py
@@ -175,7 +175,16 @@ class MySQLTableImpl(sql.TableImpl):
rowid_column = property(lambda s: s._rowid_col())
class MySQLCompiler(ansisql.ANSICompiler):
- pass
+ def limit_clause(self, select):
+ text = ""
+ if select.limit is not None:
+ text += " \n LIMIT " + str(select.limit)
+ if select.offset is not None:
+ if select.limit is None:
+ # striaght from the MySQL docs, I kid you not
+ text += " \n LIMIT 18446744073709551615"
+ text += " OFFSET " + str(select.offset)
+ return text
class MySQLSchemaGenerator(ansisql.ANSISchemaGenerator):
def get_column_specification(self, column, override_pk=False, first_pk=False):
diff --git a/lib/sqlalchemy/databases/oracle.py b/lib/sqlalchemy/databases/oracle.py
index 58d533b10..93670729d 100644
--- a/lib/sqlalchemy/databases/oracle.py
+++ b/lib/sqlalchemy/databases/oracle.py
@@ -195,6 +195,28 @@ class OracleCompiler(ansisql.ANSICompiler):
self.bindparams[c.key] = None
return ansisql.ANSICompiler.visit_insert(self, insert)
+ def visit_select(self, select):
+ """looks for LIMIT and OFFSET in a select statement, and if so tries to wrap it in a
+ subquery with rownum criterion."""
+ if getattr(select, '_oracle_visit', False):
+ ansisql.ANSICompiler.visit_select(self, select)
+ return
+ if select.limit is not None or select.offset is not None:
+ select._oracle_visit = True
+ limitselect = select.select()
+ if select.limit is not None:
+ limitselect.append_whereclause("rownum<%d" % select.limit)
+ if select.offset is not None:
+ limitselect.append_whereclause("rownum>%d" % select.offset)
+ limitselect.accept_visitor(self)
+ self.strings[select] = self.strings[limitselect]
+ self.froms[select] = self.froms[limitselect]
+ else:
+ ansisql.ANSICompiler.visit_select(self, select)
+
+ def limit_clause(self, select):
+ return ""
+
class OracleSchemaGenerator(ansisql.ANSISchemaGenerator):
def get_column_specification(self, column, override_pk=False, **kwargs):
colspec = column.name
diff --git a/lib/sqlalchemy/databases/postgres.py b/lib/sqlalchemy/databases/postgres.py
index 5a3d6388a..531e8af03 100644
--- a/lib/sqlalchemy/databases/postgres.py
+++ b/lib/sqlalchemy/databases/postgres.py
@@ -240,6 +240,16 @@ class PGCompiler(ansisql.ANSICompiler):
if c.sequence is not None and not c.sequence.optional:
self.bindparams[c.key] = None
return ansisql.ANSICompiler.visit_insert(self, insert)
+
+ def limit_clause(self, select):
+ text = ""
+ if select.limit is not None:
+ text += " \n LIMIT " + str(select.limit)
+ if select.offset is not None:
+ if select.limit is None:
+ text += " \n LIMIT ALL"
+ text += " OFFSET " + str(select.offset)
+ return text
class PGSchemaGenerator(ansisql.ANSISchemaGenerator):
def get_column_specification(self, column, override_pk=False, **kwargs):
diff --git a/lib/sqlalchemy/databases/sqlite.py b/lib/sqlalchemy/databases/sqlite.py
index 23a9d0483..fc85d92ac 100644
--- a/lib/sqlalchemy/databases/sqlite.py
+++ b/lib/sqlalchemy/databases/sqlite.py
@@ -188,7 +188,17 @@ class SQLiteCompiler(ansisql.ANSICompiler):
def __init__(self, *args, **params):
params.setdefault('paramstyle', 'named')
ansisql.ANSICompiler.__init__(self, *args, **params)
+ def limit_clause(self, select):
+ text = ""
+ if select.limit is not None:
+ text += " \n LIMIT " + str(select.limit)
+ if select.offset is not None:
+ if select.limit is None:
+ text += " \n LIMIT -1"
+ text += " OFFSET " + str(select.offset)
+ return text
+
class SQLiteSchemaGenerator(ansisql.ANSISchemaGenerator):
def get_column_specification(self, column, override_pk=False, **kwargs):
colspec = column.name + " " + column.type.get_col_spec()
diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py
index d1fd2315e..3429555d3 100644
--- a/lib/sqlalchemy/mapping/properties.py
+++ b/lib/sqlalchemy/mapping/properties.py
@@ -541,8 +541,8 @@ class EagerLoader(PropertyLoader):
# if this eagermapper is to select using an "alias" to isolate it from other
# eager mappers against the same table, we have to redefine our secondary
- # or primary join condition to reference the aliased table. else
- # we set up the target clause objects as what they are defined in the
+ # or primary join condition to reference the aliased table (and the order_by).
+ # else we set up the target clause objects as what they are defined in the
# superclass.
if self.selectalias is not None:
self.eagertarget = self.target.alias(self.selectalias)
@@ -554,11 +554,21 @@ class EagerLoader(PropertyLoader):
else:
self.eagerprimary = self.primaryjoin.copy_container()
self.eagerprimary.accept_visitor(aliasizer)
+ if self.order_by is not None:
+ self.eager_order_by = [o.copy_container() for o in self.order_by]
+ for i in range(0, len(self.eager_order_by)):
+ if isinstance(self.eager_order_by[i], schema.Column):
+ self.eager_order_by[i] = self.eagertarget._get_col_by_original(self.eager_order_by[i])
+ else:
+ self.eager_order_by[i].accept_visitor(aliasizer)
+ else:
+ self.eager_order_by = None
else:
self.eagertarget = self.target
self.eagerprimary = self.primaryjoin
self.eagersecondary = self.secondaryjoin
-
+ self.eager_order_by = self.order_by
+
def setup(self, key, statement, recursion_stack = None, **options):
"""add a left outer join to the statement thats being constructed"""
@@ -588,8 +598,8 @@ class EagerLoader(PropertyLoader):
if self.order_by is None:
statement.order_by(self.eagertarget.rowid_column)
- if self.order_by is not None:
- statement.order_by(*[self.eagertarget._get_col_by_original(c) for c in self.order_by])
+ if self.eager_order_by is not None:
+ statement.order_by(*self.eager_order_by)
statement.append_from(statement._outerjoin)
statement.append_column(self.eagertarget)
@@ -691,12 +701,18 @@ class Aliasizer(sql.ClauseVisitor):
aliasname = table.name + "_" + hex(random.randint(0, 65535))[2:]
return self.aliases.setdefault(table, sql.alias(table, aliasname))
+ def visit_compound(self, compound):
+ for i in range(0, len(compound.clauses)):
+ if isinstance(compound.clauses[i], schema.Column) and self.tables.has_key(compound.clauses[i].table):
+ compound.clauses[i] = self.get_alias(compound.clauses[i].table)._get_col_by_original(compound.clauses[i])
+ self.match = True
+
def visit_binary(self, binary):
if isinstance(binary.left, schema.Column) and self.tables.has_key(binary.left.table):
- binary.left = self.get_alias(binary.left.table).c[binary.left.name]
+ binary.left = self.get_alias(binary.left.table)._get_col_by_original(binary.left)
self.match = True
if isinstance(binary.right, schema.Column) and self.tables.has_key(binary.right.table):
- binary.right = self.get_alias(binary.right.table).c[binary.right.name]
+ binary.right = self.get_alias(binary.right.table)._get_col_by_original(binary.right)
self.match = True
class BinaryVisitor(sql.ClauseVisitor):
diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py
index 71cbacdac..10e8f3e74 100644
--- a/lib/sqlalchemy/sql.py
+++ b/lib/sqlalchemy/sql.py
@@ -552,7 +552,7 @@ class CompoundClause(ClauseList):
f += c._get_from_objects()
return f
def hash_key(self):
- return string.join([c.hash_key() for c in self.clauses], self.operator)
+ return string.join([c.hash_key() for c in self.clauses], self.operator or " ")
class Function(ClauseList, CompareMixin):
"""describes a SQL function. extends ClauseList to provide comparison operators."""
@@ -948,7 +948,7 @@ class CompoundSelect(Selectable, TailClauseMixin):
class Select(Selectable, TailClauseMixin):
"""finally, represents a SELECT statement, with appendable clauses, as well as
the ability to execute itself and return a result set."""
- def __init__(self, columns=None, whereclause = None, from_obj = [], order_by = None, group_by=None, having=None, use_labels = False, distinct=False, engine = None):
+ def __init__(self, columns=None, whereclause = None, from_obj = [], order_by = None, group_by=None, having=None, use_labels = False, distinct=False, engine = None, limit=None, offset=None):
self._columns = util.OrderedProperties()
self._froms = util.OrderedDict()
self.use_labels = use_labels
@@ -958,6 +958,8 @@ class Select(Selectable, TailClauseMixin):
self.having = None
self._engine = engine
self.rowid_column = None
+ self.limit = limit
+ self.offset = offset
# indicates if this select statement is a subquery inside another query
self.issubquery = False
diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py
index c247c86ed..d280368f6 100644
--- a/lib/sqlalchemy/util.py
+++ b/lib/sqlalchemy/util.py
@@ -23,6 +23,8 @@ def to_list(x):
return None
if not isinstance(x, list) and not isinstance(x, tuple):
return [x]
+ else:
+ return x
class OrderedProperties(object):
"""an object that maintains the order in which attributes are set upon it.