.. sqlbuilder documentation master file, created by sphinx-quickstart on Sat Sep 5 23:02:40 2015. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Lightweight Python SQLBuilder ============================= SmartSQL - lightweight Python sql builder, follows the `KISS principle `_. Supports Python2 and Python3. You can use SmartSQL separatelly, or with Django, or with super-lightweight `Ascetic ORM `_, or with super-lightweight datamapper `Openorm `_ (`miror `__) etc. * Home Page: https://bitbucket.org/emacsway/sqlbuilder * Docs: https://sqlbuilder.readthedocs.io/ * Browse source code (canonical repo): https://bitbucket.org/emacsway/sqlbuilder/src * GitHub mirror: https://github.com/emacsway/sqlbuilder * Get source code (canonical repo): ``hg clone https://bitbucket.org/emacsway/sqlbuilder`` * Get source code (mirror): ``git clone https://github.com/emacsway/sqlbuilder.git`` * PyPI: https://pypi.python.org/pypi/sqlbuilder LICENSE: * License is BSD Quick start =========== :: >>> from sqlbuilder.smartsql import Q, T, compile >>> compile(Q().tables( ... (T.book & T.author).on(T.book.author_id == T.author.id) ... ).fields( ... T.book.title, T.author.first_name, T.author.last_name ... ).where( ... (T.author.first_name != 'Tom') & (T.author.last_name != 'Smith') ... )[20:30]) ('SELECT "book"."title", "author"."first_name", "author"."last_name" FROM "book" INNER JOIN "author" ON ("book"."author_id" = "author"."id") WHERE "author"."first_name" <> %s AND "author"."last_name" <> %s LIMIT %s OFFSET %s', ['Tom', 'Smith', 10, 20]) Django integration ================== Simple add "django_sqlbuilder" to your INSTALLED_APPS. :: >>> object_list = Book.s.q.tables( ... (Book.s & Author.s).on(Book.s.author == Author.s.pk) ... ).where( ... (Author.s.first_name != 'James') & (Author.s.last_name != 'Joyce') ... )[:10] Contents: .. toctree:: :maxdepth: 10 .. contents:: Table of Contents Short manual for sqlbuilder.smartsql ==================================== Table ----- :: >>> from sqlbuilder.smartsql import T, Q >>> T.book >>> T.book.as_('a') >>> T.book__a # Same as T.book.as_('a') Compiling instance of TableAlias depends on context of usage:: >>> ta = T.book.as_('a') >>> ta >>> Q().tables(ta).fields(ta.id, ta.status).where(ta.status.in_(('new', 'approved'))) Field ----- Get field as table attribute:: >>> from sqlbuilder.smartsql import T, F, Q >>> T.book.title >>> T.book.title.as_('a') >>> T.book.title__a # Same as T.book.title.as_('a') What if field name is reserved word in table namespace? :: >>> T.book.natural # Will returns a bound method of > >>> T.book['natural'] # Will returns Field() >>> T.book.f.natural # Also >>> T.book.f['natural'] # Also >>> T.book.f('natural') # Also >>> F('natural', T.book) # Also Get field as attribute of F class (Legacy way):: >>> F.book__title # Same as T.book.title >>> F.book__title.as_('a') # Same as T.book.title.as_('a') >>> F.book__title__a # Same as T.book.title.as_('a') Compiling instance of Alias depends on context of usage:: >>> al = T.book.status.as_('a') >>> al >>> Q().tables(T.book).fields(T.book.id, al).where(al.in_(('new', 'approved'))) Table operators --------------- :: >>> (T.book & T.author).on(T.book.author_id == T.author.id) >>> (T.book + T.author).on(T.book.author_id == T.author.id) >>> (T.book - T.author).on(T.book.author_id == T.author.id) >>> (T.book | T.author).on(T.book.author_id == T.author.id) >>> (T.book * T.author).on(T.book.author_id == T.author.id) Nested join is also supported:: >>> t1, t2, t3, t4 = T.t1, T.t2, T.t3, T.t4 >>> t1 + (t2 * t3 * t4)().on((t2.a == t1.a) & (t3.b == t1.b) & (t4.c == t1.c)) >>> t1 + (t2 + t3).on((t2.b == t3.b) | t2.b.is_(None))() >>> (t1 + t2.on(t1.a == t2.a))() + t3.on((t2.b == t3.b) | t2.b.is_(None)) Condition operators ------------------- :: >>> from sqlbuilder.smartsql import T, P >>> tb = T.author >>> tb.name == 'Tom' >>> tb.name != 'Tom' %s, ['Tom']> >>> tb.counter + 1 >>> 1 + tb.counter >>> tb.counter - 1 >>> 10 - tb.counter >>> tb.counter * 2 >>> 2 * tb.counter >>> tb.counter / 2 >>> 10 / tb.counter >>> tb.is_staff & tb.is_admin >>> tb.is_staff | tb.is_admin >>> tb.counter > 10 %s, [10]> >>> 10 > tb.counter >>> tb.counter >= 10 = %s, [10]> >>> 10 >= tb.counter >>> tb.counter < 10 >>> 10 < tb.counter %s, [10]> >>> tb.counter <= 10 >>> 10 <= tb.counter = %s, [10]> >>> tb.mask << 1 >>> tb.mask >> 1 > %s, [1]> >>> tb.is_staff.is_(True) >>> tb.is_staff.is_not(True) >>> tb.status.in_(('new', 'approved')) >>> tb.status.not_in(('new', 'approved')) >>> tb.last_name.like('mi') >>> tb.last_name.ilike('mi') >>> P('mi').like(tb.last_name) >>> tb.last_name.rlike('mi') >>> tb.last_name.rilike('mi') >>> tb.last_name.startswith('Sm') >>> tb.last_name.istartswith('Sm') >>> tb.last_name.contains('mi') >>> tb.last_name.icontains('mi') >>> tb.last_name.endswith('th') >>> tb.last_name.iendswith('th') >>> tb.last_name.rstartswith('Sm') >>> tb.last_name.ristartswith('Sm') >>> tb.last_name.rcontains('mi') >>> tb.last_name.ricontains('mi') >>> tb.last_name.rendswith('th') >>> tb.last_name.riendswith('th') >>> +tb.counter >>> -tb.counter >>> ~tb.counter >>> tb.name.distinct() >>> tb.counter ** 2 >>> 2 ** tb.counter >>> tb.counter % 2 >>> 2 % tb.counter >>> abs(tb.counter) >>> tb.counter.count() >>> tb.age.between(20, 30) >>> tb.age[20:30] >>> tb.age[20] >>> tb.name.concat(' staff', ' admin') >>> tb.name.concat_ws(' ', 'staff', 'admin') >>> tb.name.op('MY_EXTRA_OPERATOR')(10) >>> tb.name.rop('MY_EXTRA_OPERATOR')(10) >>> tb.name.asc() >>> tb.name.desc() >>> ((tb.age > 25) | (tb.answers > 10)) & (tb.is_staff | tb.is_admin) %s OR "author"."answers" > %s) AND ("author"."is_staff" OR "author"."is_admin"), [25, 10]> >>> (T.author.first_name != 'Tom') & (T.author.last_name.in_(('Smith', 'Johnson'))) %s AND "author"."last_name" IN (%s, %s), ['Tom', 'Smith', 'Johnson']> >>> (T.author.first_name != 'Tom') | (T.author.last_name.in_(('Smith', 'Johnson'))) %s OR "author"."last_name" IN (%s, %s), ['Tom', 'Smith', 'Johnson']> .. module:: sqlbuilder.smartsql.contrib.evaluate :synopsis: Module sqlbuilder.smartsql.contrib.evaluate Module sqlbuilder.smartsql.contrib.evaluate ------------------------------------------- Unfortunately, Python supports limited list of operators compared to PostgreSQL. Many operators like ``@>``, ``&>``, ``-|-``, ``@-@`` and so on are not supported by Python. You can use method :meth:`Expr.op` or class :class:`Binary` to solve this problem, for example:: >>> T.user.age.op('<@')(func.int8range(25, 30)) >>> Binary(T.user.age, '<@', func.int8range(25, 30)) But this solution has a lack of readability. So, sqlbuilder provides module :mod:`sqlbuilder.smartsql.contrib.evaluate`, that allows you to mix SQL operators (like ``@>``, ``&>``, ``-|-``, ``@-@`` etc.) and python expressions. In other words, you can use SQL operators with Python expressions. For example:: >>> from sqlbuilder.smartsql.contrib.evaluate import e >>> e("T.user.age <@ func.int4range(25, 30)") or with kwargs:: >>> from sqlbuilder.smartsql.contrib.evaluate import e >>> required_range = func.int8range(25, 30) >>> e("T.user.age <@ required_range", required_range=required_range) or with context object:: >>> from sqlbuilder.smartsql.contrib.evaluate import e >>> required_range = func.int8range(25, 30) >>> e("T.user.age <@ required_range", locals()) You can pre-compile expression, similar `re.compile() `_, to avoid parsing of it each time:: >>> from sqlbuilder.smartsql.contrib.evaluate import compile, e >>> required_range = func.int8range(25, 30) >>> compiled_expr = compile("""T.user.age <@ required_range""") >>> e(compiled_expr, {'required_range': required_range}) Btw, you can even pre-compile expression into sql-string to achieve the fastest result:: >>> from sqlbuilder.smartsql import * >>> from sqlbuilder.smartsql.contrib.evaluate import e >>> sql, params = compile(e("T.user.age <@ func.int4range('%(min)s', '%(max)s')")) >>> sql = sql.replace('%%', '%%%%') % tuple(params) >>> sql u'"user"."age" <@ INT4RANGE(%(min)s, %(max)s)' More complex example:: >>> from sqlbuilder.smartsql import * >>> from sqlbuilder.smartsql.contrib.evaluate import e >>> required_range = func.int8range(25, 30) >>> e("T.user.age <@ required_range AND NOT(T.user.is_staff OR T.user.is_admin)", locals()) .. note:: Module :mod:`sqlbuilder.smartsql.contrib.evaluate` uses operator precedence similar `PostgreSQL precedence `_, not `Python precedence `__. Also note, that ``Power`` has `left association similar PostgreSQL `__, in contrast Python has right association:: $ python >>> 2 ** 3 ** 5 14134776518227074636666380005943348126619871175004951664972849610340958208L :: $ psql postgres=# SELECT 2 ^ 3 ^ 5; ?column? ---------- 32768 (1 row) Real operator precedence and association directions you can see in `source code of the module `__. .. module:: sqlbuilder.smartsql :synopsis: Module sqlbuilder.smartsql Module sqlbuilder.smartsql -------------------------- Query object ------------ .. class:: Query Query builder class .. attribute:: result Instance of :class:`Result`. See `Implementation of execution`_. .. method:: __init__([tables=None, result=None]) :param tables: Tables for FROM clause of SQL query :type tables: Table, TableAlias, TableJoin, or None :param result: Object with implementation of execution :type result: Result or None **Building methods:** .. method:: distinct(*args, **opts) Builds DISTINCT [ON (...)] clause. This method has interface similar to :meth:`~fields`. - Adds expressions if arguments exist. - Sets expressions if exists single argument of list/tuple type. - Gets expressions without arguments. - Resets expressions with ``reset=True`` keyword argument. Also can be used sole argument of boolean type: - ``True`` to apply DISTINCT clause - ``False`` to reset DISTINCT clause :return: copy of self if arguments exist, else current expression list. :rtype: Query or ExprList Example of usage:: >>> # Sole argument of boolean type >>> from sqlbuilder.smartsql import Q, T >>> q = Q().fields('*').tables(T.author) >>> q >>> bool(q.distinct()) False >>> q = q.distinct(True) >>> q >>> bool(q.distinct()) True >>> q.distinct(False) >>> q >>> # Expression list >>> from sqlbuilder.smartsql import Q, T >>> q = Q().tables(T.author).fields(T.author.first_name, T.author.last_name, T.author.age) >>> q >>> # Add expressions: >>> q = q.distinct(T.author.first_name, T.author.last_name) >>> q >>> q = q.distinct(T.author.age) >>> q >>> # Get expressions: >>> q.distinct() >>> # Set new expressions list: >>> q = q.distinct([T.author.id, T.author.status]) >>> q >>> # Reset expressions: >>> q.distinct([]) >>> q.distinct(reset=True) .. method:: fields(*args, **opts) Builds SELECT clause. - Adds fields if arguments exist. - Sets fields if exists single argument of list/tuple type. - Gets fields without arguments. - Resets fields with ``reset=True`` keyword argument. :return: copy of self if arguments exist, else current field list. :rtype: Query or FieldList Example of usage:: >>> from sqlbuilder.smartsql import * >>> q = Q().tables(T.author) >>> # Add fields: >>> q = q.fields(T.author.first_name, T.author.last_name) >>> q >>> q = q.fields(T.author.age) >>> q >>> # Get fields: >>> q.fields() >>> # Set new fields list: >>> q = q.fields([T.author.id, T.author.status]) >>> q >>> # Reset fields: >>> q = q.fields([]) >>> q >>> # Another way to reset fields: >>> q = q.fields(reset=True) >>> q .. method:: tables(tables=None) Builds FROM clause. :param tables: Can be None, Table or TableJoin instance :type tables: None, Table or TableJoin :return: copied self with new tables if ``tables`` argument is not None, else current tables. :rtype: TableJoin or Query Example of usage:: >>> from sqlbuilder.smartsql import T, Q >>> q = Q().tables(T.author).fields('*') >>> q >>> q = q.tables(T.author.as_('author_alias')) >>> q >>> q.tables() >>> q = q.tables((q.tables() + T.book).on(T.book.author_id == T.author.as_('author_alias').id)) >>> q .. method:: where(cond[, op=operator.and_]) Builds WHERE clause. - Adds new criterias using the ``op`` operator, if ``op`` is not None. - Sets new criterias if ``op`` is None. :param cond: Selection criterias :type cond: Expr :param op: Attribute of ``operator`` module or None, ``operator.and_`` by default :return: copy of self with new criteria :rtype: Query Example of usage:: >>> import operator >>> from sqlbuilder.smartsql import T, Q >>> q = Q().tables(T.author).fields('*') >>> q >>> # Add conditions >>> q = q.where(T.author.is_staff.is_(True)) >>> q >>> q = q.where(T.author.first_name == 'John') >>> q >>> q = q.where(T.author.last_name == 'Smith', op=operator.or_) >>> q >>> # Set conditions >>> q = q.where(T.author.last_name == 'Smith', op=None) >>> q .. method:: group_by(*args, **opts) Builds GROUP BY clause. This method has interface similar to :meth:`~fields`. - Adds expressions if arguments exist. - Sets expressions if exists single argument of list/tuple type. - Gets expressions without arguments. - Resets expressions with ``reset=True`` keyword argument. :return: copy of self if arguments exist, else current expression list. :rtype: Query or ExprList Example of usage:: >>> from sqlbuilder.smartsql import T, Q >>> q = Q().tables(T.author).fields('*') >>> q >>> # Add expressions: >>> q = q.group_by(T.author.first_name, T.author.last_name) >>> q >>> q = q.group_by(T.author.age) >>> q >>> # Get expressions: >>> q.group_by() >>> # Set new expressions list: >>> q = q.group_by([T.author.id, T.author.status]) >>> q >>> # Reset expressions: >>> q = q.group_by([]) >>> q >>> # Another way to reset expressions: >>> q = q.group_by(reset=True) >>> q .. method:: having(cond[, op=operator.and_]) Builds HAVING clause. This method has interface similar to :meth:`~where`. - Adds new criterias using the ``op`` operator, if ``op`` is not None. - Sets new criterias if ``op`` is None. :param cond: Selection criterias :type cond: Expr :param op: Attribute of ``operator`` module or None, ``operator.and_`` by default :return: copy of self with new criteria :rtype: Query Example of usage:: >>> import operator >>> from sqlbuilder.smartsql import T, Q >>> q = Q().fields('*').tables(T.author).group_by(T.author.status) >>> q >>> # Add conditions >>> q = q.having(T.author.is_staff.is_(True)) >>> q >>> q = q.having(T.author.first_name == 'John') >>> q >>> q = q.having(T.author.last_name == 'Smith', op=operator.or_) >>> q >>> # Set conditions >>> q = q.having(T.author.last_name == 'Smith', op=None) >>> q .. method:: order_by(*args, **opts) Builds ORDER BY clause. This method has interface similar to :meth:`~fields`. - Adds expressions if arguments exist. - Sets expressions if exists single argument of list/tuple type. - Gets expressions without arguments. - Resets expressions with ``reset=True`` keyword argument. :return: copy of self if arguments exist, else current expression list. :rtype: Query or ExprList Example of usage:: >>> from sqlbuilder.smartsql import T, Q >>> q = Q().tables(T.author).fields('*') >>> q >>> # Add expressions: >>> q = q.order_by(T.author.first_name, T.author.last_name) >>> q >>> q = q.order_by(T.author.age.desc()) >>> q >>> # Get expressions: >>> q.order_by() >>> # Set new expressions list: >>> q = q.order_by([T.author.id.desc(), T.author.status]) >>> q >>> # Reset expressions: >>> q = q.order_by([]) >>> q >>> # Another way to reset expressions: >>> q = q.order_by(reset=True) >>> q **Executing methods:** .. method:: select(*args, **opts) Example of usage:: >>> from sqlbuilder.smartsql import Q, T >>> Q(T.author).fields('*').select(for_update=True) ('SELECT * FROM "author" FOR UPDATE', []) .. method:: count() Example of usage:: >>> from sqlbuilder.smartsql import Q, T >>> Q(T.author).fields('*').count() ('SELECT COUNT(1) AS "count_value" FROM (SELECT * FROM "author") AS "count_list"', []) .. method:: insert([key_values=None, **kw]) :param key_values: Map of fields (or field names) to it's values. :type key_values: dict :param kw: Extra keyword arguments that will be passed to :class:`Insert` instance. Example of usage:: >>> from sqlbuilder.smartsql import Q, T, func >>> Q(T.stats).insert({ ... T.stats.object_type: 'author', ... T.stats.object_id: 15, ... T.stats.counter: 1 ... }, on_duplicate_key_update={ ... T.stats.counter: T.stats.counter + func.VALUES(T.stats.counter) ... }) ... ('INSERT INTO "stats" ("stats"."counter", "stats"."object_id", "stats"."object_type") VALUES (%s, %s, %s) ON CONFLICT DO UPDATE SET "stats"."counter" = "stats"."counter" + VALUES("stats"."counter")', [1, 15, 'author']) >>> # Keys of dict are strings >>> Q(T.stats).insert({ ... 'object_type': 'author', ... 'object_id': 15, ... 'counter': 1 ... }, on_duplicate_key_update={ ... 'counter': T.stats.counter + func.VALUES(T.stats.counter) ... }) ... ('INSERT INTO "stats" ("object_type", "object_id", "counter") VALUES (%s, %s, %s) ON CONFLICT DO UPDATE SET "counter" = "stats"."counter" + VALUES("stats"."counter")', ['author', 15, 1]) >>> # Use "values" keyword argument >>> Q().fields( ... T.stats.object_type, T.stats.object_id, T.stats.counter ... ).tables(T.stats).insert( ... values=('author', 15, 1), ... on_duplicate_key_update={T.stats.counter: T.stats.counter + func.VALUES(T.stats.counter)} ... ) ... ('INSERT INTO "stats" ("stats"."object_type", "stats"."object_id", "stats"."counter") VALUES %s, %s, %s ON CONFLICT DO UPDATE SET "stats"."counter" = "stats"."counter" + VALUES("stats"."counter")', ['author', 15, 1]) >>> # Insert many >>> Q().fields( ... T.stats.object_type, T.stats.object_id, T.stats.counter ... ).tables(T.stats).insert( ... values=( ... ('author', 15, 1), ... ('author', 16, 1), ... ), ... on_duplicate_key_update={T.stats.counter: T.stats.counter + func.VALUES(T.stats.counter)} ... ) ... ('INSERT INTO "stats" ("stats"."object_type", "stats"."object_id", "stats"."counter") VALUES (%s, %s, %s), (%s, %s, %s) ON CONFLICT DO UPDATE SET "stats"."counter" = "stats"."counter" + VALUES("stats"."counter")', ['author', 15, 1, 'author', 16, 1]) >>> # Insert ignore >>> Q().fields( ... T.stats.object_type, T.stats.object_id, T.stats.counter ... ).tables(T.stats).insert( ... values=('author', 15, 1), ... ignore=True ... ) ... ('INSERT INTO "stats" ("stats"."object_type", "stats"."object_id", "stats"."counter") VALUES %s, %s, %s ON CONFLICT DO NOTHING', ['author', 15, 1]) >>> # INSERT ... SELECT Syntax >>> Q().fields( ... T.stats.object_type, T.stats.object_id, T.stats.counter ... ).tables(T.stats).insert( ... values=Q().fields( ... T.old_stats.object_type, T.old_stats.object_id, T.old_stats.counter ... ).tables(T.old_stats), ... on_duplicate_key_update={ ... T.stats.counter: T.stats.counter + T.old_stats.counter, ... } ... ) ('INSERT INTO "stats" ("stats"."object_type", "stats"."object_id", "stats"."counter") SELECT "old_stats"."object_type", "old_stats"."object_id", "old_stats"."counter" FROM "old_stats" ON CONFLICT DO UPDATE SET "stats"."counter" = "stats"."counter" + "old_stats"."counter"', []) .. method:: update([key_values=None, **kw]) :param key_values: Map of fields (or field names) to it's values. :type key_values: dict :param kw: Extra keyword arguments that will be passed to :class:`Update` instance. Example of usage:: >>> from sqlbuilder.smartsql import Q, T, func >>> Q(T.author).where(T.author.id == 10).update({ ... T.author.first_name: 'John', ... T.author.last_login: func.NOW() ... }) ... ('UPDATE "author" SET "author"."last_login" = NOW(), "author"."first_name" = %s WHERE "author"."id" = %s', ['John', 10]) >>> # Keys of dict are strings >>> Q(T.author).where(T.author.id == 10).update({ ... 'first_name': 'John', ... 'last_login': func.NOW() ... }) ... ('UPDATE "author" SET "first_name" = %s, "last_login" = NOW() WHERE "author"."id" = %s', ['John', 10]) >>> # Use "values" keyword argument >>> Q(T.author).fields( ... T.author.first_name, T.author.last_login ... ).where(T.author.id == 10).update( ... values=('John', func.NOW()) ... ) ... ('UPDATE "author" SET "author"."first_name" = %s, "author"."last_login" = NOW() WHERE "author"."id" = %s', ['John', 10]) .. method:: delete(**kw) :param kw: Extra keyword arguments that will be passed to :class:`Delete` instance. Example of usage:: >>> from sqlbuilder.smartsql import Q, T >>> Q(T.author).where(T.author.id == 10).delete() ('DELETE FROM "author" WHERE "author"."id" = %s', [10]) .. method:: as_table(alias) Returns current query as table reference. :param str kw: alias name. Example of usage:: >>> from sqlbuilder.smartsql import T, Q >>> author_query_alias = Q(T.author).fields(T.author.id).where( ... T.author.status == 'active' ... ).as_table('author_query_alias') >>> Q().fields(T.book.id, T.book.title).tables( ... (T.book & author_query_alias ... ).on( ... T.book.author_id == author_query_alias.id ... )) ... .. method:: as_set([all=False]) Returns current query as set, to can be combined with other queries using the set operations union, intersection, and difference. :param bool all: eliminates duplicate rows from result, if all is False. Example of usage:: >>> from sqlbuilder.smartsql import T, Q >>> q1 = Q(T.book1).fields(T.book1.id, T.book1.title).where(T.book1.author_id == 10) >>> q2 = Q(T.book2).fields(T.book2.id, T.book2.title).where(T.book2.author_id == 10) >>> q1.as_set() | q2 >>> q1.as_set() | q2 >>> q1.as_set() & q2 >>> q1.as_set() - q2 >>> q1.as_set(all=True) | q2 >>> q1.as_set(all=True) & q2 >>> q1.as_set(all=True) - q2 Subquery ^^^^^^^^ :class:`Query` extends :class:`Expr`, so, it can be used as usual expression:: >>> from sqlbuilder.smartsql import Q, T >>> # Subquery as condition >>> sub_q = Q().fields(T.author.id).tables(T.author).where(T.author.status == 'active') >>> Q().fields(T.book.id).tables(T.book).where(T.book.author_id.in_(sub_q)) >>> # Subquery as field >>> sub_q = Q().fields( ... T.book.id.count().as_("book_count") ... ).tables(T.book).where( ... T.book.pub_date > '2015-01-01' ... ).group_by(T.book.author_id) ... >>> Q().fields( ... T.author.id, sub_q.where(T.book.author_id == T.author.id) ... ).tables(T.author).where( ... T.author.status == 'active' ... ) ... %s AND "book"."author_id" = "author"."id" GROUP BY "book"."author_id") FROM "author" WHERE "author"."status" = %s, ['2015-01-01', 'active']> >>> # Subquery as alias >>> alias = Q().fields( ... T.book.id.count() ... ).tables(T.book).where( ... (T.book.pub_date > '2015-01-01') & ... (T.book.author_id == T.author.id) ... ).group_by( ... T.book.author_id ... ).as_("book_count") ... >>> Q().fields( ... T.author.id, alias ... ).tables(T.author).where( ... T.author.status == 'active' ... ).order_by(alias.desc()) ... %s AND "book"."author_id" = "author"."id" GROUP BY "book"."author_id") AS "book_count" FROM "author" WHERE "author"."status" = %s ORDER BY "book_count" DESC, ['2015-01-01', 'active']> See also method :meth:`Query.as_table`. .. _implementation-of-execution: Implementation of execution --------------------------- Class :class:`~Query` uses "`Bridge pattern `_" for implementation of execution. Module :mod:`sqlbuilder.smartsql` has default implementation in class :class:`Result` for demonstration purposes, that does only one thing - returns a tuple with SQL string and parameters. You can develop your own implementation, or, at least, specify what same compiler to use, for example:: >>> from sqlbuilder.smartsql import T, Q, Result >>> from sqlbuilder.smartsql.dialects.mysql import compile as mysql_compile >>> Q(result=Result(compile=mysql_compile)).fields(T.author.id, T.author.name).tables(T.author).select() ('SELECT `author`.`id`, `author`.`name` FROM `author`', []) See also examples of implementation in `Django integration `__ or `Ascetic ORM integration `__ Instance of :class:`Query` also delegates all unknown methods and properties to :attr:`Query.result`. Example:: >>> from sqlbuilder.smartsql import T, Q, Result >>> from sqlbuilder.smartsql.dialects.mysql import compile as mysql_compile >>> class CustomResult(Result): ... custom_attr = 5 ... def custom_method(self, arg1, arg2): ... return (self._query, arg1, arg2) ... def find_by_name(self, name): ... return self._query.where(T.author.name == name) ... >>> q = Q(result=CustomResult(compile=mysql_compile)).fields(T.author.id, T.author.name).tables(T.author) >>> q.custom_attr 5 >>> q.custom_method(5, 10) (, 5, 10) >>> q.find_by_name('John') .. class :: Result Default implementation of execution for :class:`Query` class. .. attribute:: compile instance of :class:`Compiler`, :func:`sqlbuilder.smartsql.compile` by default .. attribute:: _query Current :class:`Query` instance .. method:: __init__([compile=None]) :param compile: Compiler to compile SQL string :type compile: Compiler or None Compilers --------- There are three compilers for three dialects: .. function:: sqlbuilder.smartsql.compile(expr, [state=None]) It's a default compiler for PostgreSQL dialect, instance of :class:`Compiler`. It also used for `representation `__ of expressions. :param expr: Expression to be compiled :type expr: Expr :param state: Instance of :class:`State` or None :type state: State or None :return: If state is None, then returns tuple with SQL string and list of parameters. Else returns None. :rtype: tuple or None .. function:: sqlbuilder.smartsql.dialects.mysql.compile(expr, [state=None]) Compiler for MySQL dialect. .. function:: sqlbuilder.smartsql.dialects.sqlite.compile(expr, [state=None]) Compiler for SQLite dialect. .. module:: sqlbuilder.mini :synopsis: Module sqlbuilder.mini Short manual for sqlbuilder.mini ================================ The package contains yet another, extremely lightweight sql builder - :mod:`sqlbuilder.mini`, especially for Raw-SQL fans. It's just a hierarchical list of SQL strings, no more. Such form of presentation allows modify query without syntax analysis. You can use `sqlparse `__ library to parse SQL-string to hierarchical list, see, for example, module :mod:`sqlbuilder.mini.parser`. By the way, you can use this library to change SQL directly, - it has very nice API like DOM manipulation. You can also parse SQL to DOM or `etree `__, to have navigation by `XPath `__. Also you can use `pyparsing `__ library to `parse SQL-string to hierarchical list `__. :: >>> from sqlbuilder.mini import P, Q, compile >>> sql = [ ... 'SELECT', [ ... 'author.id', 'author.first_name', 'author.last_name' ... ], ... 'FROM', [ ... 'author', 'INNER JOIN', ['book as b', 'ON', 'b.author_id = author.id'] ... ], ... 'WHERE', [ ... 'b.status', '==', P('new') ... ], ... 'ORDER BY', [ ... 'author.first_name', 'author.last_name' ... ] ... ] >>> # Let change query >>> sql[sql.index('SELECT') + 1].append('author.age') >>> compile(sql) ('SELECT author.id, author.first_name, author.last_name, author.age FROM author INNER JOIN book as b ON b.author_id = author.id WHERE b.status == %s ORDER BY author.first_name, author.last_name', ['new']) To facilitate navigation and change SQL, there is helper :class:`sqlbuilder.mini.Q`:: >>> sql = [ ... 'SELECT', [ ... 'author.id', 'author.first_name', 'author.last_name' ... ], ... 'FROM', [ ... 'author', 'INNER JOIN', [ ... '(', 'SELECT', [ ... 'book.title' ... ], ... 'FROM', [ ... 'book' ... ], ... ')', 'AS b', 'ON', 'b.author_id = author.id' ... ], ... ], ... 'WHERE', [ ... 'b.status', '==', P('new') ... ], ... 'ORDER BY', [ ... 'author.first_name', 'author.last_name' ... ] ... ] >>> sql = Q(sql) >>> sql.prepend_child( ... ['FROM', 'INNER JOIN', 'SELECT'], # path ... ['book.id', 'book.pages'] # values to append ... ) >>> sql.append_child( ... ['FROM', 'INNER JOIN', 'SELECT'], ... ['book.date'] ... ) >>> sql.insert_after( ... ['FROM', 'INNER JOIN', (list, 1), ], ... ['WHERE', ['b.pages', '>', P(100)]] ... ) >>> sql.insert_before( ... ['FROM', 'INNER JOIN', 'WHERE', 'b.pages'], ... ['b.pages', '<', P(500), 'AND'] ... ) >>> compile(sql) ('SELECT author.id, author.first_name, author.last_name FROM author INNER JOIN ( SELECT book.id, book.pages, book.title, book.date FROM book WHERE b.pages < %s AND b.pages > %s ) AS b ON b.author_id = author.id WHERE b.status == %s ORDER BY author.first_name, author.last_name', [500, 100, 'new']) As step of path can be used: - Exact string: ['FROM', 'INNER JOIN', 'SELECT'] - Callable object, that returns index: ['FROM', 'INNER JOIN', (lambda i, item, collection: item == 'SELECT')] - Index as integer (from zero): ['FROM', 'INNER JOIN', 1] - Slice: ['FROM', 'INNER JOIN', slice(2, 5)] - Each item: ['FROM', enumerate, 'SELECT'] - Compiled Regular Expression Objects: ['FROM', 'INNER JOIN', re.compile("^SELECT$")] Also it's possible combine rules. Filter result by each next rules: ['FROM', 'INNER JOIN', ('SELECT', 0)] is egual to ['FROM', 'INNER JOIN', Filter(Exact('SELECT'), Index(0))] Union results matched by any rules: ['FROM', 'INNER JOIN', Union(Exact('SELECT'), Index(1))]. Intersect results matched by all rules: ['FROM', 'INNER JOIN', Intersect(Exact('SELECT'), Index(1))]. The difference between Filter and Union in that, Filter handles only collection of matched elements by previous rules, and Union - a full collection. P.S.: See also `article about SQLBuilder in English `__ and `in Russian `__. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search`