Skip to content

Commit

Permalink
Fix work with Oracle and added new 'schema' attribute to Model's Meta (
Browse files Browse the repository at this point in the history
…#1156)

* add 'schema' field to Model's Meta and use it; fix connection to Oracle DB by adding database info into DBQ parameter in connection string; fix typo

* Fixed ORA-01435 error while using Oracle database (#1155)

* update docs

* format and fix changelog
  • Loading branch information
thakryptex committed Jun 15, 2022
1 parent 5ea2a00 commit 722ac7c
Show file tree
Hide file tree
Showing 19 changed files with 130 additions and 24 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ Changelog
====
0.19.2
------
Added
^^^^^
- Added `schema` attribute to Model's Meta to specify exact schema to use with the model.
Fixed
^^^^^
- Mixin does not work. (#1133)
- `using_db` wrong position in model shortcut methods. (#1150)
- Fixed connection to `Oracle` database by adding database info to DBQ in connection string.
- Fixed ORA-01435 error while using `Oracle` database (#1155)

0.19.1
------
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Contributors
* Vinay Karanam ``@vinayinvicible``
* Aleksandr Rozum ``@rozumalex``
* Mojix Coder ``@MojixCoder``
* Paul Serov ``@thakryptex``

Special Thanks
==============
Expand Down
12 changes: 9 additions & 3 deletions docs/databases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ MySQL optional parameters are pass-though parameters to the driver, see `here <h
MSSQL/Oracle
============

DB URL is typically in the form of :samp:`mssql or oracle://myuser:mypass@db.host:1433/somedb?driver=the odbc driver`
DB URL is typically in the form of :samp:`mssql or oracle://myuser:mypass@db.host:1433/somedb?driver={the odbc driver}`

Required Parameters
-------------------
Expand All @@ -195,7 +195,7 @@ Required Parameters
``database``:
Database to use.
``driver``:
The ODBC driver to use.
The ODBC driver to use. Actual name of the ODBC driver in your `odbcinst.ini` file (you can find it's location using `odbcinst -j` command). It requires `unixodbc` to be installed in your system.

Optional parameters:
--------------------
Expand All @@ -209,7 +209,13 @@ MSSQL/Oracle optional parameters are pass-though parameters to the driver, see `
``pool_recycle`` (defaults to ``-1``):
Pool recycle timeout in seconds.
``echo`` (defaults to ``False``):
Set to `True`` to echo SQL queries (debug only)
Set to ``True`` to echo SQL queries (debug only)

Encoding in Oracle:
============

If you get ``???`` values in Varchar fields instead of your actual text (russian/chinese/etc), then set ``NLS_LANG`` variable in your client environment to support UTF8. For example, `"American_America.UTF8"`.


Passing in custom SSL Certificates
==================================
Expand Down
5 changes: 5 additions & 0 deletions docs/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ The ``Meta`` class

Set to ``True`` to indicate this is an abstract class

.. attribute:: schema
:annotation: = ""

Set this to configure a schema name, where table exists

.. attribute:: table
:annotation: = ""

Expand Down
11 changes: 10 additions & 1 deletion tests/contrib/test_pydantic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import copy

from tests.testmodels import Address, Employee, Event, JSONFields, Reporter, Team, Tournament, User
from tests.testmodels import (
Address,
Employee,
Event,
JSONFields,
Reporter,
Team,
Tournament,
User,
)
from tortoise.contrib import test
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator

Expand Down
9 changes: 8 additions & 1 deletion tests/test_filtering.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import datetime

from tests.testmodels import DatetimeFields, Event, IntFields, Reporter, Team, Tournament
from tests.testmodels import (
DatetimeFields,
Event,
IntFields,
Reporter,
Team,
Tournament,
)
from tortoise.contrib import test
from tortoise.contrib.test.condition import NotEQ
from tortoise.expressions import F, Q
Expand Down
10 changes: 9 additions & 1 deletion tests/test_queryset.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from tests.testmodels import Event, IntFields, MinRelation, Node, Reporter, Tournament, Tree
from tests.testmodels import (
Event,
IntFields,
MinRelation,
Node,
Reporter,
Tournament,
Tree,
)
from tortoise import connections
from tortoise.contrib import test
from tortoise.contrib.test.condition import NotEQ
Expand Down
6 changes: 5 additions & 1 deletion tests/test_unique_together.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from tests.testmodels import Tournament, UniqueTogetherFields, UniqueTogetherFieldsWithFK
from tests.testmodels import (
Tournament,
UniqueTogetherFields,
UniqueTogetherFieldsWithFK,
)
from tortoise.contrib import test
from tortoise.exceptions import IntegrityError

Expand Down
4 changes: 2 additions & 2 deletions tortoise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,8 @@ def _build_initial_querysets(cls) -> None:
for app in cls.apps.values():
for model in app.values():
model._meta.finalise_model()
model._meta.basetable = Table(model._meta.db_table)
model._meta.basequery = model._meta.db.query_class.from_(model._meta.db_table)
model._meta.basetable = Table(name=model._meta.db_table, schema=model._meta.schema)
model._meta.basequery = model._meta.db.query_class.from_(model._meta.basetable)
model._meta.basequery_all_fields = model._meta.basequery.select(
*model._meta.db_fields
)
Expand Down
5 changes: 4 additions & 1 deletion tortoise/backends/asyncpg/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
TransactionContext,
TransactionContextPooled,
)
from tortoise.backends.base_postgres.client import BasePostgresClient, translate_exceptions
from tortoise.backends.base_postgres.client import (
BasePostgresClient,
translate_exceptions,
)
from tortoise.exceptions import (
DBConnectionError,
IntegrityError,
Expand Down
2 changes: 1 addition & 1 deletion tortoise/backends/base/config_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def expand_db_url(db_url: str, testing: bool = False) -> dict:
raise ConfigurationError("Port is not an integer")
if vmap.get("username"):
# Pass username as None, instead of empty string,
# to let asyncpg retrieve username from evionment variable or OS user
# to let asyncpg retrieve username from environment variable or OS user
params[vmap["username"]] = url.username or None
if vmap.get("password"):
# asyncpg accepts None for password, but aiomysql not
Expand Down
12 changes: 11 additions & 1 deletion tortoise/backends/base_postgres/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import abc
from functools import wraps
from typing import Any, Callable, List, Optional, SupportsInt, Tuple, Type, TypeVar, Union
from typing import (
Any,
Callable,
List,
Optional,
SupportsInt,
Tuple,
Type,
TypeVar,
Union,
)

from pypika import PostgreSQLQuery

Expand Down
12 changes: 10 additions & 2 deletions tortoise/backends/mssql/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

from pypika.dialects import MSSQLQuery

from tortoise.backends.base.client import Capabilities, TransactionContext, TransactionContextPooled
from tortoise.backends.base.client import (
Capabilities,
TransactionContext,
TransactionContextPooled,
)
from tortoise.backends.mssql.executor import MSSQLExecutor
from tortoise.backends.mssql.schema_generator import MSSQLSchemaGenerator
from tortoise.backends.odbc.client import ODBCClient, ODBCTransactionWrapper, translate_exceptions
from tortoise.backends.odbc.client import (
ODBCClient,
ODBCTransactionWrapper,
translate_exceptions,
)


class MSSQLClient(ODBCClient):
Expand Down
18 changes: 13 additions & 5 deletions tortoise/backends/oracle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
TransactionContext,
TransactionContextPooled,
)
from tortoise.backends.odbc.client import ODBCClient, ODBCTransactionWrapper, translate_exceptions
from tortoise.backends.odbc.client import (
ODBCClient,
ODBCTransactionWrapper,
translate_exceptions,
)
from tortoise.backends.oracle.executor import OracleExecutor
from tortoise.backends.oracle.schema_generator import OracleSchemaGenerator

Expand All @@ -42,8 +46,12 @@ def __init__(
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
self.user = user.upper()
self.password = password
self.dsn = f"DRIVER={driver};DBQ={host}:{port};UID={user};PWD={password};"
dbq = f"{host}:{port}"
if self.database:
dbq += f"/{self.database}"
self.dsn = f"DRIVER={driver};DBQ={dbq};UID={user};PWD={password};"

def _in_transaction(self) -> "TransactionContext":
return TransactionContextPooled(TransactionWrapper(self))
Expand Down Expand Up @@ -93,16 +101,16 @@ def _timestamp_convert(self, value: bytes) -> datetime.date:

async def __aenter__(self):
connection = await super(OraclePoolConnectionWrapper, self).__aenter__() # type: ignore
if self.client._template.get("database") and not hasattr(connection, "current_schema"):
await connection.execute(f'ALTER SESSION SET CURRENT_SCHEMA = "{self.client.database}"')
if getattr(self.client, "database", False) and not hasattr(connection, "current_schema"):
await connection.execute(f'ALTER SESSION SET CURRENT_SCHEMA = "{self.client.user}"')
await connection.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'")
await connection.execute(
"ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD\"T\"HH24:MI:SSTZH:TZM'"
)
await connection.add_output_converter(
pyodbc.SQL_TYPE_TIMESTAMP, self._timestamp_convert
)
setattr(connection, "current_schema", self.client.database)
setattr(connection, "current_schema", self.client.user)
return connection


Expand Down
6 changes: 5 additions & 1 deletion tortoise/backends/sqlite/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
)
from tortoise.backends.sqlite.executor import SqliteExecutor
from tortoise.backends.sqlite.schema_generator import SqliteSchemaGenerator
from tortoise.exceptions import IntegrityError, OperationalError, TransactionManagementError
from tortoise.exceptions import (
IntegrityError,
OperationalError,
TransactionManagementError,
)

FuncType = Callable[..., Any]
F = TypeVar("F", bound=FuncType)
Expand Down
19 changes: 17 additions & 2 deletions tortoise/expressions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import operator
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Tuple, Type, Union, cast
from typing import (
TYPE_CHECKING,
Any,
Dict,
Iterator,
List,
Optional,
Tuple,
Type,
Union,
cast,
)

from pypika import Case as PypikaCase
from pypika import Field as PypikaField
Expand All @@ -11,7 +22,11 @@
from pypika.utils import format_alias_sql

from tortoise.exceptions import ConfigurationError, FieldError, OperationalError
from tortoise.fields.relational import BackwardFKRelation, ForeignKeyFieldInstance, RelationalField
from tortoise.fields.relational import (
BackwardFKRelation,
ForeignKeyFieldInstance,
RelationalField,
)
from tortoise.query_utils import QueryModifier, _get_joins_for_related_field

if TYPE_CHECKING: # pragma: nocoverage
Expand Down
9 changes: 8 additions & 1 deletion tortoise/fields/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from tortoise.fields.base import CASCADE, NO_ACTION, RESTRICT, SET_DEFAULT, SET_NULL, Field
from tortoise.fields.base import (
CASCADE,
NO_ACTION,
RESTRICT,
SET_DEFAULT,
SET_NULL,
Field,
)
from tortoise.fields.data import (
BigIntField,
BinaryField,
Expand Down
2 changes: 2 additions & 0 deletions tortoise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class MetaInfo:
__slots__ = (
"abstract",
"db_table",
"schema",
"app",
"fields",
"db_fields",
Expand Down Expand Up @@ -217,6 +218,7 @@ def __init__(self, meta: "Model.Meta") -> None:
self.abstract: bool = getattr(meta, "abstract", False)
self.manager: Manager = getattr(meta, "manager", Manager())
self.db_table: str = getattr(meta, "table", "")
self.schema: Optional[str] = getattr(meta, "schema", None)
self.app: Optional[str] = getattr(meta, "app", None)
self.unique_together: Tuple[Tuple[str, ...], ...] = get_together(meta, "unique_together")
self.indexes: Tuple[Tuple[str, ...], ...] = get_together(meta, "indexes")
Expand Down
6 changes: 5 additions & 1 deletion tortoise/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
from pypika.terms import Criterion

from tortoise.exceptions import OperationalError
from tortoise.fields.relational import BackwardFKRelation, ManyToManyFieldInstance, RelationalField
from tortoise.fields.relational import (
BackwardFKRelation,
ManyToManyFieldInstance,
RelationalField,
)

if TYPE_CHECKING: # pragma: nocoverage
from tortoise.queryset import QuerySet
Expand Down

0 comments on commit 722ac7c

Please sign in to comment.