"""
pgmock.selector
---------------
Contains the primary functionality for chainable SQL selectors
"""
import pgmock.exceptions
import pgmock.mocker
import pgmock.render
def body():
"""Obtains the body of a selector.
When applicable, this selector returns the body of another selection.
For example, a ``CREATE TABLE new_table AS SELECT * FROM other_table``
has a body of ``SELECT * FROM other_table``.
Returns:
Selector: A chainable SQL selector.
Examples:
Obtain the body of a ``CREATE TABLE AS`` expression
.. code-block:: python
body = pgmock.sql(sql_string, pgmock.create_table_as('table').body())
Obtain the body of an ``INSERT INTO`` expression using the syntax of
passing multiple selectors to ``pgmock.sql``
.. code-block:: python
body = pgmock.sql(sql_string, pgmock.insert_into('table'), pgmock.body())
Note:
This selector should only be used to refine another selector, such as
``pgmock.create_table_as`` or ``pgmock.insert_into``. In other words, calling:
.. code-block:: python
body = pgmock.sql(sql_string, pgmock.body())
will result in an error.
"""
return Selector().body()
[docs]def statement(start, end=None):
"""Obtains a statement selector.
Statements are naively parsed by splitting SQL based on the semicolon.
If any semicolons exist in the comments or literal strings, this
selector has undefined behavior.
Args:
start (int): The starting statement. If ``end`` is ``None``,
obtain a single statement
end (int, optional): The ending statement (exclusive)
Returns:
Selector: A chainable SQL selector.
Raises:
`StatementParseError`: When the statement range is invalid for the
parsed statements.
Examples:
Obtain the first statement in a SQL string
.. code-block:: python
statement = pgmock.sql(sql_string, pgmock.statement(0))
Obtain the second and third statements in a SQL string
.. code-block:: python
statement = pgmock.sql(sql_string, pgmock.statement(1, 3))
"""
return Selector().statement(start, end=end)
[docs]def insert_into(table):
"""Obtains a selector for an ``INSERT INTO`` expression.
Searches for ``INSERT INTO table_name(optional columns)`` and returns
the entire statement. The body of the statement (e.g. the ``SELECT`` or anything after
``INSERT INTO``) can be returned by chaining the ``body()`` selector.
Args:
table (str): The table of the expression.
Returns:
Selector: A chainable SQL selector.
Raises:
`NoMatchError`: When the expression cannot be found during rendering.
`MultipleMatchError`: When multiple expressions are found during rendering.
Examples:
Obtain the ``INSERT INTO`` of table "t"
.. code-block:: python
insert_into = pgmock.sql(sql_string, pgmock.insert_into('t'))
Obtain the body of the ``INSERT INTO`` of table "t"
.. code-block:: python
insert_into_body = pgmock.sql(sql_string, pgmock.insert_into('t').body())
Note:
When patching ``INSERT INTO`` statements, the entire body of the statement after the
``INSERT INTO`` is patched
"""
return Selector().insert_into(table)
[docs]def cte(alias):
"""Obtains a selector for a common table expression (CTE)
CTEs are matched by searching for a ``WITH cte_name AS`` or for searching
for a CTE after a comma (e.g ``WITH cte_name1 AS ..., cte_name2 AS ...``)
Args:
alias (str): The alias of the CTE
Returns:
Selector: A chainable SQL selector
Raises:
`NoMatchError`: When the CTE cannot be found during rendering.
`MultipleMatchError`: When multiple CTEs are found during rendering.
`NestedMatchError`: When nested subquery matches are found during rendering.
`InvalidSQLError`: When enclosing parentheses for a CTE
cannot be found.
Examples:
Obtain the CTE that has the alias "a"
.. code-block:: python
cte = pgmock.sql(sql_string, pgmock.cte('a'))
"""
return Selector().cte(alias)
[docs]def subquery(alias):
"""Obtains a selector for a subquery
Subqueries are matched by an alias preceeded by an enclosing
parenthesis. Once matched, the SQL is search for the starting
parenthesis.
Args:
alias (str): The alias of the subquery.
Returns:
Selector: A chainable SQL selector.
Raises:
`NoMatchError`: When the expression cannot be found during rendering.
`MultipleMatchError`: When multiple expressions are found during rendering.
`NestedMatchError`: When nested subquery matches are found during rendering.
`InvalidSQLError`: When enclosing parentheses for a subquery
cannot be found.
Examples:
Obtain the subquery that has the alias "a"
.. code-block:: python
subquery = pgmock.sql(sql_string, pgmock.subquery('a'))
Todo:
- Support for subqueries without an alias (e.g. after an "in" keyword)
"""
return Selector().subquery(alias)
[docs]def table(name, alias=None):
"""Obtains a selector for a table
Tables are matched by searching for their name and optional
aliases after a ``FROM`` or ``JOIN`` keyword. If the table has an alias
but the alias isn't provided, a `NoMatchError` will be thrown.
Args:
name (str): The name of the table (including the schema if in the query)
alias (str, optional): The alias of the table if it exists
Returns:
Selector: A chainable SQL selector.
Raises:
`NoMatchError`: When the expression cannot be found during rendering.
`MultipleMatchError`: When multiple expressions are found during rendering.
Examples:
Obtain the table with no alias that has the name "schema.table_name"
.. code-block:: python
table = pgmock.sql(sql_string, pgmock.table('schema.table_name'))
Obtain the table with the name "schema.table_name" that has the alias "a"
.. code-block:: python
table = pgmock.sql(sql_string, pgmock.table('schema.table_name', 'a'))
Todo:
- Support lateral joins and other joins that have keywords after
the ``JOIN`` keyword
"""
return Selector().table(name, alias=alias)
[docs]def create_table_as(table):
"""Obtains a selector for a ``CREATE TABLE AS`` statement.
Searches for ``CREATE TABLE table_name(optional columns) AS`` and returns
the entire statement. The body of the statement (e.g. the ``SELECT`` or anything after
``CREATE TABLE AS``) can be returned by chaining the ``body()`` selector.
Args:
table (str): The name of the table as referenced in the expression
Returns:
Selector: A chainable SQL selector.
Raises:
`NoMatchError`: When the expression cannot be found during rendering.
`MultipleMatchError`: When multiple expressions are found during rendering.
Examples:
Obtain the ``CREATE TABLE AS`` of table "t"
.. code-block:: python
ctas = pgmock.sql(sql_string, pgmock.create_table_as('t'))
Obtain the body of the ``CREATE TABLE AS`` of table "t"
.. code-block:: python
ctas_body = pgmock.sql(sql_string, pgmock.create_table_as('t').body())
Note:
When patching ``CREATE TABLE AS`` statements, the entire body of the statement is patched
with a ``SELECT * FROM VALUES ... AS pgmock(columns...)``. This is because it is illegal
to do ``VALUES ... AS ...`` after a "create table as" statement.
"""
return Selector().create_table_as(table)
class Selector(pgmock.render.Renderable):
"""A selector for targetting expressions in SQL.
Methods can be chained to represent what is being targetted, for example a
subquery in the first statement::
Selector().statement(0).subquery('alias')
Once ``patch`` is applied to a selector, a ``Mock`` object is returned and
only ``patch`` can be chained.
"""
@pgmock.render.Renderable.chainable_render_method
def __getitem__(self, index):
pass
@pgmock.render.Renderable.chainable_render_method
def statement(self, start, end=None):
pass
@pgmock.render.Renderable.chainable_render_method
def body(self):
pass
@pgmock.render.Renderable.chainable_render_method
def cte(self, alias):
pass
@pgmock.render.Renderable.chainable_render_method
def insert_into(self, table):
pass
@pgmock.render.Renderable.chainable_render_method
def subquery(self, alias):
pass
@pgmock.render.Renderable.chainable_render_method
def table(self, table, alias=None):
pass
@pgmock.render.Renderable.chainable_render_method
def create_table_as(self, table):
pass
def patch(self, *args, **kwargs):
return pgmock.mocker.Mocker(renderable=self).patch(*args, **kwargs)