From: Mike Bayer Date: Sat, 6 Dec 2008 18:27:04 +0000 (+0000) Subject: - postgres docstring X-Git-Tag: rel_0_5_0~139 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=994ab27aa38dfd3dc627083338509519247b7e20;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - postgres docstring - insert/update/delete are documented generatively - values({}) is no longer deprecated, thus enabling unicode/Columns as keys --- diff --git a/doc/build/reference/sqlalchemy/expressions.rst b/doc/build/reference/sqlalchemy/expressions.rst index e25875760f..acafa87478 100644 --- a/doc/build/reference/sqlalchemy/expressions.rst +++ b/doc/build/reference/sqlalchemy/expressions.rst @@ -143,7 +143,7 @@ Classes :show-inheritance: .. autoclass:: Delete - :members: + :members: where :show-inheritance: .. autoclass:: FromClause @@ -151,7 +151,7 @@ Classes :show-inheritance: .. autoclass:: Insert - :members: + :members: prefix_with, values :show-inheritance: .. autoclass:: Join @@ -171,7 +171,7 @@ Classes :show-inheritance: .. autoclass:: Update - :members: + :members: where, values :show-inheritance: .. _generic_functions: diff --git a/doc/build/sqlexpression.rst b/doc/build/sqlexpression.rst index 621b33bb0d..fea1cce002 100644 --- a/doc/build/sqlexpression.rst +++ b/doc/build/sqlexpression.rst @@ -102,13 +102,13 @@ To see a sample of the SQL this construct produces, use the ``str()`` function:: >>> str(ins) 'INSERT INTO users (id, name, fullname) VALUES (:id, :name, :fullname)' -Notice above that the INSERT statement names every column in the ``users`` table. This can be limited by using the ``values`` keyword, which establishes the VALUES clause of the INSERT explicitly:: +Notice above that the INSERT statement names every column in the ``users`` table. This can be limited by using the ``values()`` method, which establishes the VALUES clause of the INSERT explicitly:: - >>> ins = users.insert(values={'name':'jack', 'fullname':'Jack Jones'}) + >>> ins = users.insert().values(name='jack', fullname='Jack Jones') >>> str(ins) 'INSERT INTO users (name, fullname) VALUES (:name, :fullname)' -Above, while the ``values`` keyword limited the VALUES clause to just two columns, the actual data we placed in ``values`` didn't get rendered into the string; instead we got named bind parameters. As it turns out, our data *is* stored within our ``Insert`` construct, but it typically only comes out when the statement is actually executed; since the data consists of literal values, SQLAlchemy automatically generates bind parameters for them. We can peek at this data for now by looking at the compiled form of the statement:: +Above, while the ``values`` method limited the VALUES clause to just two columns, the actual data we placed in ``values`` didn't get rendered into the string; instead we got named bind parameters. As it turns out, our data *is* stored within our ``Insert`` construct, but it typically only comes out when the statement is actually executed; since the data consists of literal values, SQLAlchemy automatically generates bind parameters for them. We can peek at this data for now by looking at the compiled form of the statement:: >>> ins.compile().params #doctest: +NORMALIZE_WHITESPACE {'fullname': 'Jack Jones', 'name': 'jack'} @@ -379,7 +379,7 @@ The ``7`` literal is embedded in ``ClauseElement``; we can use the same trick we .. sourcecode:: pycon+sql >>> (users.c.id==7).compile().params - {'id_1': 7} + {u'id_1': 7} Most Python operators, as it turns out, produce a SQL expression here, like equals, not equals, etc.: @@ -833,7 +833,7 @@ If we wanted to use our ``calculate`` statement twice with different bind parame WHERE users.id BETWEEN c1.z AND c2.z >>> s.compile().params - {'x_2': 5, 'y_2': 12, 'y_1': 45, 'x_1': 17} + {u'x_2': 5, u'y_2': 12, u'y_1': 45, u'x_1': 17} See also :attr:`sqlalchemy.sql.expression.func`. @@ -977,22 +977,22 @@ Finally, we're back to UPDATE. Updates work a lot like INSERTS, except there is .. sourcecode:: pycon+sql >>> # change 'jack' to 'ed' - {sql}>>> conn.execute(users.update(users.c.name=='jack', values={'name':'ed'})) #doctest: +ELLIPSIS + {sql}>>> conn.execute(users.update().where(users.c.name=='jack').values(name='ed')) #doctest: +ELLIPSIS UPDATE users SET name=? WHERE users.name = ? ['ed', 'jack'] COMMIT {stop} >>> # use bind parameters - >>> u = users.update(users.c.name==bindparam('oldname'), values={'name':bindparam('newname')}) + >>> u = users.update().where(users.c.name==bindparam('oldname')).values(name=bindparam('newname')) {sql}>>> conn.execute(u, oldname='jack', newname='ed') #doctest: +ELLIPSIS UPDATE users SET name=? WHERE users.name = ? ['ed', 'jack'] COMMIT {stop} - >>> # update a column to an expression - {sql}>>> conn.execute(users.update(values={users.c.fullname:"Fullname: " + users.c.name})) #doctest: +ELLIPSIS + >>> # update a column to an expression. Send a dictionary to values(): + {sql}>>> conn.execute(users.update().values({users.c.fullname:"Fullname: " + users.c.name})) #doctest: +ELLIPSIS UPDATE users SET fullname=(? || users.name) ['Fullname: '] COMMIT @@ -1001,13 +1001,12 @@ Finally, we're back to UPDATE. Updates work a lot like INSERTS, except there is Correlated Updates ------------------ - A correlated update lets you update a table using selection from another table, or the same table: .. sourcecode:: pycon+sql >>> s = select([addresses.c.email_address], addresses.c.user_id==users.c.id).limit(1) - {sql}>>> conn.execute(users.update(values={users.c.fullname:s})) #doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE + {sql}>>> conn.execute(users.update().values({users.c.fullname:s})) #doctest: +ELLIPSIS,+NORMALIZE_WHITESPACE UPDATE users SET fullname=(SELECT addresses.email_address FROM addresses WHERE addresses.user_id = users.id @@ -1030,7 +1029,7 @@ Finally, a delete. Easy enough: COMMIT {stop} - {sql}>>> conn.execute(users.delete(users.c.name > 'm')) #doctest: +ELLIPSIS + {sql}>>> conn.execute(users.delete().where(users.c.name > 'm')) #doctest: +ELLIPSIS DELETE FROM users WHERE users.name > ? ['m'] COMMIT diff --git a/lib/sqlalchemy/databases/oracle.py b/lib/sqlalchemy/databases/oracle.py index d71b129d1f..72a0bb2650 100644 --- a/lib/sqlalchemy/databases/oracle.py +++ b/lib/sqlalchemy/databases/oracle.py @@ -8,39 +8,44 @@ Oracle version 8 through current (11g at the time of this writing) are supported. Driver +------ The Oracle dialect uses the cx_oracle driver, available at -http://python.net/crew/atuining/cx_Oracle/ . The dialect has several behaviors +http://cx-oracle.sourceforge.net/ . The dialect has several behaviors which are specifically tailored towards compatibility with this module. Connecting +---------- Connecting with create_engine() uses the standard URL approach of -oracle://user:pass@host:port/dbname[?key=value&key=value...]. If dbname is present, the +``oracle://user:pass@host:port/dbname[?key=value&key=value...]``. If dbname is present, the host, port, and dbname tokens are converted to a TNS name using the cx_oracle -makedsn() function. Otherwise, the host token is taken directly as a TNS name. +:func:`makedsn()` function. Otherwise, the host token is taken directly as a TNS name. Additional arguments which may be specified either as query string arguments on the -URL, or as keyword arguments to create_engine() include: +URL, or as keyword arguments to :func:`~sqlalchemy.create_engine()` are: - mode - This is given the string value of SYSDBA or SYSOPER, or alternatively an - integer value. This value is only available as a URL query string argument. +* *allow_twophase* - enable two-phase transactions. Defaults to ``True``. - allow_twophase - enable two-phase transactions. This feature is not yet supported. +* *auto_convert_lobs* - defaults to True, see the section on LOB objects. - threaded - defaults to True with SQLAlchemy, enable multithreaded access to - cx_oracle connections. +* *auto_setinputsizes* - the cx_oracle.setinputsizes() call is issued for all bind parameters. + This is required for LOB datatypes but can be disabled to reduce overhead. Defaults + to ``True``. - use_ansi - defaults to True, use ANSI JOIN constructs (see the section on Oracle 8). +* *mode* - This is given the string value of SYSDBA or SYSOPER, or alternatively an + integer value. This value is only available as a URL query string argument. - auto_convert_lobs - defaults to True, see the section on LOB objects. +* *threaded* - enable multithreaded access to cx_oracle connections. Defaults + to ``True``. Note that this is the opposite default of cx_oracle itself. - auto_setinputsizes - the cx_oracle.setinputsizes() call is issued for all bind parameters. - This is required for LOB datatypes but can be disabled to reduce overhead. +* *use_ansi* - Use ANSI JOIN constructs (see the section on Oracle 8). Defaults + to ``True``. If ``False``, Oracle-8 compatible constructs are used for joins. - optimize_limits - defaults to False, see the section on LIMIT/OFFSET. +* *optimize_limits* - defaults to ``False``. see the section on LIMIT/OFFSET. Auto Increment Behavior +----------------------- SQLAlchemy Table objects which include integer primary keys are usually assumed to have "autoincrementing" behavior, meaning they can generate their own primary key values upon @@ -63,6 +68,7 @@ This step is also required when using table reflection, i.e. autoload=True:: ) LOB Objects +----------- cx_oracle presents some challenges when fetching LOB objects. A LOB object in a result set is presented by cx_oracle as a cx_oracle.LOB object which has a read() method. By default, @@ -84,6 +90,7 @@ without raising cursor errors. The conversion of LOB in all cases, as well as t of LOB objects, can be disabled using auto_convert_lobs=False. LIMIT/OFFSET Support +-------------------- Oracle has no support for the LIMIT or OFFSET keywords. Whereas previous versions of SQLAlchemy used the "ROW NUMBER OVER..." construct to simulate LIMIT/OFFSET, SQLAlchemy 0.5 now uses @@ -94,18 +101,20 @@ this was stepping into the bounds of optimization that is better left on the DBA prefix can be added by enabling the optimize_limits=True flag on create_engine(). Two Phase Transaction Support +----------------------------- -Two Phase transactions are partially implemented using XA transactions but at the time of this -writing have not been successfully tested. The author of cx_oracle also stated that he's never -seen them work so this may be a cx_oracle issue. +Two Phase transactions are implemented using XA transactions. Success has been reported of them +working successfully but this should be regarded as an experimental feature. Oracle 8 Compatibility +---------------------- When using Oracle 8, a "use_ansi=False" flag is available which converts all JOIN phrases into the WHERE clause, and in the case of LEFT OUTER JOIN makes use of Oracle's (+) operator. Synonym/DBLINK Reflection +------------------------- When using reflection with Table objects, the dialect can optionally search for tables indicated by synonyms that reference DBLINK-ed tables by passing the flag diff --git a/lib/sqlalchemy/databases/postgres.py b/lib/sqlalchemy/databases/postgres.py index 57620c007c..320c749bc9 100644 --- a/lib/sqlalchemy/databases/postgres.py +++ b/lib/sqlalchemy/databases/postgres.py @@ -6,17 +6,87 @@ """Support for the PostgreSQL database. -PostgreSQL supports partial indexes. To create them pass a posgres_where +Driver +------ + +The psycopg2 driver is supported, available at http://pypi.python.org/pypi/psycopg2/ . +The dialect has several behaviors which are specifically tailored towards compatibility +with this module. + +Note that psycopg1 is **not** supported. + +Connecting +---------- + +URLs are of the form `postgres://user@password@host:port/dbname[?key=value&key=value...]`. + +Postgres-specific keyword arguments which are accepted by :func:`~sqlalchemy.create_engine()` are: + +* *server_side_cursors* - Enable the usage of "server side cursors" for SQL statements which support + this feature. What this essentially means from a psycopg2 point of view is that the cursor is + created using a name, e.g. `connection.cursor('some name')`, which has the effect that result rows + are not immediately pre-fetched and buffered after statement execution, but are instead left + on the server and only retrieved as needed. SQLAlchemy's :class:`~sqlalchemy.engine.base.ResultProxy` + uses special row-buffering behavior when this feature is enabled, such that groups of 100 rows + at a time are fetched over the wire to reduce conversational overhead. + +Sequences/SERIAL +---------------- + +Postgres supports sequences, and SQLAlchemy uses these as the default means of creating +new primary key values for integer-based primary key columns. When creating tables, +SQLAlchemy will issue the ``SERIAL`` datatype for integer-based primary key columns, +which generates a sequence corresponding to the column and associated with it based on +a naming convention. + +To specify a specific named sequence to be used for primary key generation, use the +:func:`~sqlalchemy.schema.Sequence` construct:: + + Table('sometable', metadata, + Column('id', Integer, Sequence('some_id_seq'), primary_key=True) + ) + +Currently, when SQLAlchemy issues a single insert statement, to fulfill the contract of +having the "last insert identifier" available, the sequence is executed independently +beforehand and the new value is retrieved, to be used in the subsequent insert. Note +that when an :func:`~sqlalchemy.sql.expression.insert()` construct is executed using +"executemany" semantics, the sequence is not pre-executed and normal PG SERIAL behavior +is used. + +Postgres 8.3 supports an ``INSERT...RETURNING`` syntax which SQLAlchemy supports +as well. A future release of SQLA will use this feature by default in lieu of +sequence pre-execution in order to retrieve new primary key values, when available. + +INSERT/UPDATE...RETURNING +------------------------- + +The dialect supports PG 8.3's ``INSERT..RETURNING`` and ``UPDATE..RETURNING`` syntaxes, +but must be explicitly enabled on a per-statement basis:: + + # INSERT..RETURNING + result = table.insert(postgres_returning=[table.c.col1, table.c.col2]).\\ + values(name='foo') + print result.fetchall() + + # UPDATE..RETURNING + result = table.update(postgres_returning=[table.c.col1, table.c.col2]).\\ + where(table.c.name=='foo').values(name='bar') + print result.fetchall() + +Indexes +------- + +PostgreSQL supports partial indexes. To create them pass a postgres_where option to the Index constructor:: Index('my_index', my_table.c.id, postgres_where=tbl.c.value > 10) -PostgreSQL 8.2+ supports returning a result set from inserts and updates. -To use this pass the column/expression list to the postgres_returning -parameter when creating the queries:: +Transactions +------------ + +The Postgres dialect fully supports SAVEPOINT and two-phase commit operations. + - raises = tbl.update(empl.c.sales > 100, values=dict(salary=empl.c.salary * 1.1), - postgres_returning=[empl.c.id, empl.c.salary]).execute().fetchall() """ import decimal, random, re, string diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 4a1fab166b..8d2e7f8114 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -268,23 +268,21 @@ def insert(table, values=None, inline=False, **kwargs): Similar functionality is available via the ``insert()`` method on :class:`~sqlalchemy.schema.Table`. - table - The table to be inserted into. + :param table: The table to be inserted into. - values - A dictionary which specifies the column specifications of the + :param values: A dictionary which specifies the column specifications of the ``INSERT``, and is optional. If left as None, the column specifications are determined from the bind parameters used during the compile phase of the ``INSERT`` statement. If the bind parameters also are None during the compile phase, then the column specifications will be generated from the full list of - table columns. + table columns. Note that the :meth:`~Insert.values()` generative method + may also be used for this. - prefixes - A list of modifier keywords to be inserted between INSERT and INTO, - see ``Insert.prefix_with``. + :param prefixes: A list of modifier keywords to be inserted between INSERT and INTO. + Alternatively, the :meth:`~Insert.prefix_with` generative method may be used. - inline + :param inline: if True, SQL defaults will be compiled 'inline' into the statement and not pre-executed. @@ -312,27 +310,26 @@ def update(table, whereclause=None, values=None, inline=False, **kwargs): Similar functionality is available via the ``update()`` method on :class:`~sqlalchemy.schema.Table`. - table - The table to be updated. + :param table: The table to be updated. - whereclause - A ``ClauseElement`` describing the ``WHERE`` condition of the - ``UPDATE`` statement. + :param whereclause: A ``ClauseElement`` describing the ``WHERE`` condition of the + ``UPDATE`` statement. Note that the :meth:`~Update.where()` generative + method may also be used for this. - values + :param values: A dictionary which specifies the ``SET`` conditions of the ``UPDATE``, and is optional. If left as None, the ``SET`` conditions are determined from the bind parameters used during the compile phase of the ``UPDATE`` statement. If the bind parameters also are None during the compile phase, then the ``SET`` conditions will be generated from the full list of table - columns. + columns. Note that the :meth:`~Update.values()` generative method may + also be used for this. - inline + :param inline: if True, SQL defaults will be compiled 'inline' into the statement and not pre-executed. - If both `values` and compile-time bind parameters are present, the compile-time bind parameters override the information specified within `values` on a per-key basis. @@ -357,12 +354,11 @@ def delete(table, whereclause = None, **kwargs): Similar functionality is available via the ``delete()`` method on :class:`~sqlalchemy.schema.Table`. - table - The table to be updated. + :param table: The table to be updated. - whereclause - A ``ClauseElement`` describing the ``WHERE`` condition of the - ``UPDATE`` statement. + :param whereclause: A :class:`ClauseElement` describing the ``WHERE`` condition of the + ``UPDATE`` statement. Note that the :meth:`~Delete.where()` generative method + may be used instead. """ return Delete(table, whereclause, **kwargs) @@ -522,15 +518,15 @@ def exists(*args, **kwargs): Calling styles are of the following forms:: # use on an existing select() - s = select([]).where() + s = select([table.c.col1]).where(table.c.col2==5) s = exists(s) # construct a select() at once - exists(['*'], **select_arguments).where() + exists(['*'], **select_arguments).where(criterion) # columns argument is optional, generates "EXISTS (SELECT *)" # by default. - exists().where() + exists().where(table.c.col2==5) """ return _Exists(*args, **kwargs) @@ -2841,12 +2837,18 @@ class TableClause(_Immutable, FromClause): return select([func.count(col).label('tbl_row_count')], whereclause, from_obj=[self], **params) def insert(self, values=None, inline=False, **kwargs): + """Genrate an :func:`insert()` construct.""" + return insert(self, values=values, inline=inline, **kwargs) def update(self, whereclause=None, values=None, inline=False, **kwargs): + """Genrate an :func:`update()` construct.""" + return update(self, whereclause=whereclause, values=values, inline=inline, **kwargs) def delete(self, whereclause=None, **kwargs): + """Genrate a :func:`delete()` construct.""" + return delete(self, whereclause, **kwargs) @property @@ -3487,7 +3489,8 @@ class _ValuesBase(_UpdateBase): key= arguments \*args - deprecated. A single dictionary can be sent as the first positional argument. + A single dictionary can be sent as the first positional argument. This allows + non-string based keys, such as Column objects, to be used. """ if args: @@ -3504,6 +3507,11 @@ class _ValuesBase(_UpdateBase): self.parameters.update(kwargs) class Insert(_ValuesBase): + """Represent an INSERT construct. + + The ``Insert`` object is created using the :func:`insert()` function. + + """ def __init__(self, table, values=None, inline=False, bind=None, prefixes=None, **kwargs): _ValuesBase.__init__(self, table, values) self._bind = bind @@ -3537,6 +3545,11 @@ class Insert(_ValuesBase): self._prefixes = self._prefixes + [clause] class Update(_ValuesBase): + """Represent an Update construct. + + The ``Update`` object is created using the :func:`update()` function. + + """ def __init__(self, table, whereclause, values=None, inline=False, bind=None, **kwargs): _ValuesBase.__init__(self, table, values) self._bind = bind @@ -3570,6 +3583,12 @@ class Update(_ValuesBase): class Delete(_UpdateBase): + """Represent a DELETE construct. + + The ``Delete`` object is created using the :func:`delete()` function. + + """ + def __init__(self, table, whereclause, bind=None, **kwargs): self._bind = bind self.table = table