Remove Elixir library
Update SQLAlchemy
This commit is contained in:
+319
-135
@@ -1,23 +1,20 @@
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Column, ForeignKey, Table, Index
|
||||
from sqlalchemy.ext.associationproxy import AssociationProxy
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm import relationship, object_mapper, ColumnProperty, class_mapper
|
||||
from sqlalchemy.orm.exc import UnmappedInstanceError
|
||||
from sqlalchemy.orm.query import Query
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from couchpotato.core.helpers.encoding import toUnicode
|
||||
from elixir.entity import Entity
|
||||
from elixir.fields import Field
|
||||
from elixir.options import options_defaults, using_options
|
||||
from elixir.relationships import ManyToMany, OneToMany, ManyToOne
|
||||
from sqlalchemy.ext.mutable import Mutable
|
||||
from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, String, \
|
||||
TypeDecorator
|
||||
import json
|
||||
import time
|
||||
|
||||
options_defaults["shortnames"] = True
|
||||
|
||||
# We would like to be able to create this schema in a specific database at
|
||||
# will, so we can test it easily.
|
||||
# Make elixir not bind to any session to make this possible.
|
||||
#
|
||||
# http://elixir.ematia.de/trac/wiki/Recipes/MultipleDatabasesOneMetadata
|
||||
__session__ = None
|
||||
|
||||
|
||||
class SetEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
@@ -73,74 +70,227 @@ class MutableDict(Mutable, dict):
|
||||
|
||||
MutableDict.associate_with(JsonType)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
COLUMN_BLACKLIST = ('_sa_polymorphic_on', )
|
||||
|
||||
def is_mapped_class(cls):
|
||||
try:
|
||||
class_mapper(cls)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_like_list(instance, relation):
|
||||
"""Returns ``True`` if and only if the relation of `instance` whose name is
|
||||
`relation` is list-like.
|
||||
|
||||
A relation may be like a list if, for example, it is a non-lazy one-to-many
|
||||
relation, or it is a dynamically loaded one-to-many.
|
||||
|
||||
"""
|
||||
if relation in instance._sa_class_manager:
|
||||
return instance._sa_class_manager[relation].property.uselist
|
||||
related_value = getattr(type(instance), relation, None)
|
||||
return isinstance(related_value, AssociationProxy)
|
||||
|
||||
class TableHelper():
|
||||
def to_dict(self, deep = None, exclude = None, include = None,
|
||||
exclude_relations = None, include_relations = None,
|
||||
include_methods = None):
|
||||
instance = self
|
||||
|
||||
if (exclude is not None or exclude_relations is not None) and \
|
||||
(include is not None or include_relations is not None):
|
||||
raise ValueError('Cannot specify both include and exclude.')
|
||||
# create a list of names of columns, including hybrid properties
|
||||
try:
|
||||
columns = [p.key for p in object_mapper(instance).iterate_properties
|
||||
if isinstance(p, ColumnProperty)]
|
||||
except UnmappedInstanceError:
|
||||
return instance
|
||||
for parent in type(instance).mro():
|
||||
columns += [key for key, value in parent.__dict__.items()
|
||||
if isinstance(value, hybrid_property)]
|
||||
# filter the columns based on exclude and include values
|
||||
if exclude is not None:
|
||||
columns = (c for c in columns if c not in exclude)
|
||||
elif include is not None:
|
||||
columns = (c for c in columns if c in include)
|
||||
# create a dictionary mapping column name to value
|
||||
result = dict((col, getattr(instance, col)) for col in columns
|
||||
if not (col.startswith('__') or col in COLUMN_BLACKLIST))
|
||||
# add any included methods
|
||||
if include_methods is not None:
|
||||
result.update(dict((method, getattr(instance, method)()) for method in include_methods if not '.' in method))
|
||||
# Check for objects in the dictionary that may not be serializable by
|
||||
# default. Specifically, convert datetime and date objects to ISO 8601
|
||||
# format, and convert UUID objects to hexadecimal strings.
|
||||
for key, value in result.items():
|
||||
# TODO We can get rid of this when issue #33 is resolved.
|
||||
if isinstance(value, datetime.date):
|
||||
result[key] = value.isoformat()
|
||||
elif isinstance(value, uuid.UUID):
|
||||
result[key] = str(value)
|
||||
elif is_mapped_class(type(value)):
|
||||
result[key] = value.to_dict()
|
||||
# recursively call _to_dict on each of the `deep` relations
|
||||
deep = deep or {}
|
||||
for relation, rdeep in deep.items():
|
||||
# Get the related value so we can see if it is None, a list, a query
|
||||
# (as specified by a dynamic relationship loader), or an actual
|
||||
# instance of a model.
|
||||
relatedvalue = getattr(instance, relation)
|
||||
if relatedvalue is None:
|
||||
result[relation] = None
|
||||
continue
|
||||
# Determine the included and excluded fields for the related model.
|
||||
newexclude = None
|
||||
newinclude = None
|
||||
if exclude_relations is not None and relation in exclude_relations:
|
||||
newexclude = exclude_relations[relation]
|
||||
elif (include_relations is not None and
|
||||
relation in include_relations):
|
||||
newinclude = include_relations[relation]
|
||||
# Determine the included methods for the related model.
|
||||
newmethods = None
|
||||
if include_methods is not None:
|
||||
newmethods = [method.split('.', 1)[1] for method in include_methods
|
||||
if method.split('.', 1)[0] == relation]
|
||||
if is_like_list(instance, relation):
|
||||
result[relation] = [inst.to_dict(rdeep, exclude = newexclude,
|
||||
include = newinclude,
|
||||
include_methods = newmethods)
|
||||
for inst in relatedvalue]
|
||||
continue
|
||||
# If the related value is dynamically loaded, resolve the query to get
|
||||
# the single instance.
|
||||
if isinstance(relatedvalue, Query):
|
||||
relatedvalue = relatedvalue.one()
|
||||
result[relation] = relatedvalue.to_dict(rdeep, exclude = newexclude,
|
||||
include = newinclude,
|
||||
include_methods = newmethods)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
movie_files = Table('movie_files__file_movie', Base.metadata,
|
||||
Column('movie_id', Integer, ForeignKey('movie.id'), nullable = False),
|
||||
Column('file_id', Integer, ForeignKey('file.id'), nullable = False),
|
||||
Index('movie_files_idx', 'movie_id', 'file_id', unique = True)
|
||||
)
|
||||
|
||||
release_files = Table('release_files__file_release', Base.metadata,
|
||||
Column('release_id', Integer, ForeignKey('release.id'), nullable = False),
|
||||
Column('file_id', Integer, ForeignKey('file.id'), nullable = False),
|
||||
Index('release_files_idx', 'release_id', 'file_id', unique = True)
|
||||
)
|
||||
|
||||
library_files = Table('library_files__file_library', Base.metadata,
|
||||
Column('library_id', Integer, ForeignKey('library.id'), nullable = False),
|
||||
Column('file_id', Integer, ForeignKey('file.id'), nullable = False),
|
||||
Index('library_files_idx', 'library_id', 'file_id', unique = True)
|
||||
)
|
||||
|
||||
class Movie(Base, TableHelper):
|
||||
__tablename__ = 'movie'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
class Movie(Entity):
|
||||
"""Movie Resource a movie could have multiple releases
|
||||
The files belonging to the movie object are global for the whole movie
|
||||
such as trailers, nfo, thumbnails"""
|
||||
|
||||
last_edit = Field(Integer, default = lambda: int(time.time()), index = True)
|
||||
last_edit = Column(Integer, default = lambda: int(time.time()), index = True)
|
||||
type = 'movie' # Compat tv branch
|
||||
|
||||
library = ManyToOne('Library', cascade = 'delete, delete-orphan', single_parent = True)
|
||||
status = ManyToOne('Status')
|
||||
profile = ManyToOne('Profile')
|
||||
category = ManyToOne('Category')
|
||||
releases = OneToMany('Release', cascade = 'all, delete-orphan')
|
||||
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
|
||||
library_id = Column(Integer, ForeignKey('library.id'), index = True)
|
||||
status_id = Column(Integer, ForeignKey('status.id'), index = True)
|
||||
profile_id = Column(Integer, ForeignKey('profile.id'), index = True)
|
||||
category_id = Column(Integer, ForeignKey('category.id'), index = True)
|
||||
|
||||
library = relationship('Library') #cascade = 'delete, delete-orphan', single_parent = True)
|
||||
status = relationship('Status')
|
||||
profile = relationship('Profile')
|
||||
category = relationship('Category')
|
||||
releases = relationship('Release') #, cascade = 'all, delete-orphan')
|
||||
files = relationship('File', secondary = movie_files) #, cascade = 'all, delete-orphan', single_parent = True)
|
||||
|
||||
Media = Movie # Compat tv branch
|
||||
|
||||
|
||||
class Library(Entity):
|
||||
class Library(Base, TableHelper):
|
||||
__tablename__ = 'library'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
""""""
|
||||
|
||||
year = Field(Integer)
|
||||
identifier = Field(String(20), index = True)
|
||||
year = Column(Integer)
|
||||
identifier = Column(String(20), index = True)
|
||||
|
||||
plot = Field(UnicodeText)
|
||||
tagline = Field(UnicodeText(255))
|
||||
info = Field(JsonType)
|
||||
plot = Column(UnicodeText)
|
||||
tagline = Column(UnicodeText(255))
|
||||
info = Column(JsonType)
|
||||
|
||||
status = ManyToOne('Status')
|
||||
movies = OneToMany('Movie', cascade = 'all, delete-orphan')
|
||||
titles = OneToMany('LibraryTitle', cascade = 'all, delete-orphan')
|
||||
files = ManyToMany('File', cascade = 'all, delete-orphan', single_parent = True)
|
||||
status_id = Column(Integer, ForeignKey('status.id'), index = True)
|
||||
status = relationship('Status')
|
||||
|
||||
movies = relationship('Movie') #, cascade = 'all, delete-orphan')
|
||||
titles = relationship('LibraryTitle', order_by="desc(LibraryTitle.default)") #, cascade = 'all, delete-orphan')
|
||||
files = relationship('File', secondary = library_files) #, cascade = 'all, delete-orphan', single_parent = True)
|
||||
|
||||
|
||||
class LibraryTitle(Entity):
|
||||
""""""
|
||||
using_options(order_by = '-default')
|
||||
class LibraryTitle(Base, TableHelper):
|
||||
__tablename__ = 'librarytitle'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
title = Field(Unicode)
|
||||
simple_title = Field(Unicode, index = True)
|
||||
default = Field(Boolean, default = False, index = True)
|
||||
|
||||
language = OneToMany('Language')
|
||||
libraries = ManyToOne('Library')
|
||||
|
||||
|
||||
class Language(Entity):
|
||||
""""""
|
||||
|
||||
identifier = Field(String(20), index = True)
|
||||
label = Field(Unicode)
|
||||
#using_options(order_by = '-default')
|
||||
|
||||
titles = ManyToOne('LibraryTitle')
|
||||
title = Column(Unicode)
|
||||
simple_title = Column(Unicode, index = True)
|
||||
default = Column(Boolean, default = False, index = True)
|
||||
|
||||
language = relationship('Language')
|
||||
|
||||
libraries_id = Column(Integer, ForeignKey('library.id'), index = True)
|
||||
libraries = relationship('Library')
|
||||
|
||||
|
||||
class Release(Entity):
|
||||
class Language(Base, TableHelper):
|
||||
__tablename__ = 'language'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
""""""
|
||||
|
||||
identifier = Column(String(20), index = True)
|
||||
label = Column(Unicode)
|
||||
|
||||
titles_id = Column(Integer, ForeignKey('librarytitle.id'), index = True)
|
||||
titles = relationship('LibraryTitle')
|
||||
|
||||
|
||||
class Release(Base, TableHelper):
|
||||
__tablename__ = 'release'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""Logically groups all files that belong to a certain release, such as
|
||||
parts of a movie, subtitles."""
|
||||
|
||||
last_edit = Field(Integer, default = lambda: int(time.time()), index = True)
|
||||
identifier = Field(String(100), index = True)
|
||||
last_edit = Column(Integer, default = lambda: int(time.time()), index = True)
|
||||
identifier = Column(String(100), index = True)
|
||||
|
||||
movie = ManyToOne('Movie')
|
||||
status = ManyToOne('Status')
|
||||
quality = ManyToOne('Quality')
|
||||
files = ManyToMany('File')
|
||||
info = OneToMany('ReleaseInfo', cascade = 'all, delete-orphan')
|
||||
movie_id = Column(Integer, ForeignKey('movie.id'), index = True)
|
||||
movie = relationship('Movie')
|
||||
|
||||
status_id = Column(Integer, ForeignKey('status.id'), index = True)
|
||||
status = relationship('Status')
|
||||
|
||||
quality_id = Column(Integer, ForeignKey('quality.id'), index = True)
|
||||
quality = relationship('Quality')
|
||||
|
||||
files = relationship('File', secondary = release_files)
|
||||
info = relationship('ReleaseInfo') #, cascade = 'all, delete-orphan')
|
||||
|
||||
def to_dict(self, deep = None, exclude = None):
|
||||
if not exclude: exclude = []
|
||||
@@ -162,51 +312,66 @@ class Release(Entity):
|
||||
return orig_dict
|
||||
|
||||
|
||||
class ReleaseInfo(Entity):
|
||||
class ReleaseInfo(Base, TableHelper):
|
||||
__tablename__ = 'releaseinfo'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""Properties that can be bound to a file for off-line usage"""
|
||||
|
||||
identifier = Field(String(50), index = True)
|
||||
value = Field(Unicode(255), nullable = False)
|
||||
identifier = Column(String(50), index = True)
|
||||
value = Column(Unicode(255), nullable = False)
|
||||
|
||||
release = ManyToOne('Release')
|
||||
release_id = Column(Integer, ForeignKey('release.id'), index = True)
|
||||
release = relationship('Release')
|
||||
|
||||
|
||||
class Status(Entity):
|
||||
class Status(Base, TableHelper):
|
||||
__tablename__ = 'status'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""The status of a release, such as Downloaded, Deleted, Wanted etc"""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(Unicode(20))
|
||||
identifier = Column(String(20), unique = True)
|
||||
label = Column(Unicode(20))
|
||||
|
||||
releases = OneToMany('Release')
|
||||
movies = OneToMany('Movie')
|
||||
releases = relationship('Release')
|
||||
movies = relationship('Movie')
|
||||
|
||||
|
||||
class Quality(Entity):
|
||||
class Quality(Base, TableHelper):
|
||||
__tablename__ = 'quality'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""Quality name of a release, DVD, 720p, DVD-Rip etc"""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
label = Field(Unicode(20))
|
||||
order = Field(Integer, default = 0, index = True)
|
||||
#using_options(order_by = 'order')
|
||||
|
||||
size_min = Field(Integer)
|
||||
size_max = Field(Integer)
|
||||
identifier = Column(String(20), unique = True)
|
||||
label = Column(Unicode(20))
|
||||
order = Column(Integer, default = 0, index = True)
|
||||
|
||||
releases = OneToMany('Release')
|
||||
profile_types = OneToMany('ProfileType')
|
||||
size_min = Column(Integer)
|
||||
size_max = Column(Integer)
|
||||
|
||||
releases = relationship('Release')
|
||||
profile_types = relationship('ProfileType', order_by="asc(ProfileType.order)")
|
||||
|
||||
|
||||
class Profile(Entity):
|
||||
class Profile(Base, TableHelper):
|
||||
__tablename__ = 'profile'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
""""""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
label = Field(Unicode(50))
|
||||
order = Field(Integer, default = 0, index = True)
|
||||
core = Field(Boolean, default = False)
|
||||
hide = Field(Boolean, default = False)
|
||||
#using_options(order_by = 'order')
|
||||
|
||||
movie = OneToMany('Movie')
|
||||
types = OneToMany('ProfileType', cascade = 'all, delete-orphan')
|
||||
label = Column(Unicode(50))
|
||||
order = Column(Integer, default = 0, index = True)
|
||||
core = Column(Boolean, default = False)
|
||||
hide = Column(Boolean, default = False)
|
||||
|
||||
movie = relationship('Movie')
|
||||
types = relationship('ProfileType', order_by="asc(ProfileType.order)") #, cascade = 'all, delete-orphan')
|
||||
|
||||
def to_dict(self, deep = None, exclude = None):
|
||||
if not exclude: exclude = []
|
||||
@@ -219,100 +384,119 @@ class Profile(Entity):
|
||||
return orig_dict
|
||||
|
||||
|
||||
class Category(Entity):
|
||||
class Category(Base, TableHelper):
|
||||
__tablename__ = 'category'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
""""""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
label = Field(Unicode(50))
|
||||
order = Field(Integer, default = 0, index = True)
|
||||
required = Field(Unicode(255))
|
||||
preferred = Field(Unicode(255))
|
||||
ignored = Field(Unicode(255))
|
||||
destination = Field(Unicode(255))
|
||||
#using_options(order_by = 'order')
|
||||
|
||||
movie = OneToMany('Movie')
|
||||
label = Column(Unicode(50))
|
||||
order = Column(Integer, default = 0, index = True)
|
||||
required = Column(Unicode(255))
|
||||
preferred = Column(Unicode(255))
|
||||
ignored = Column(Unicode(255))
|
||||
destination = Column(Unicode(255))
|
||||
|
||||
movie = relationship('Movie')
|
||||
|
||||
|
||||
class ProfileType(Entity):
|
||||
class ProfileType(Base, TableHelper):
|
||||
__tablename__ = 'profiletype'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
""""""
|
||||
using_options(order_by = 'order')
|
||||
|
||||
order = Field(Integer, default = 0, index = True)
|
||||
finish = Field(Boolean, default = True)
|
||||
wait_for = Field(Integer, default = 0)
|
||||
#using_options(order_by = 'order')
|
||||
|
||||
quality = ManyToOne('Quality')
|
||||
profile = ManyToOne('Profile')
|
||||
order = Column(Integer, default = 0, index = True)
|
||||
finish = Column(Boolean, default = True)
|
||||
wait_for = Column(Integer, default = 0)
|
||||
|
||||
quality_id = Column(Integer, ForeignKey('quality.id'), index = True)
|
||||
quality = relationship('Quality')
|
||||
|
||||
profile_id = Column(Integer, ForeignKey('profile.id'), index = True)
|
||||
profile = relationship('Profile')
|
||||
|
||||
|
||||
class File(Entity):
|
||||
class File(Base, TableHelper):
|
||||
__tablename__ = 'file'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""File that belongs to a release."""
|
||||
|
||||
path = Field(Unicode(255), nullable = False, unique = True)
|
||||
part = Field(Integer, default = 1)
|
||||
available = Field(Boolean, default = True)
|
||||
path = Column(Unicode(255), nullable = False, unique = True)
|
||||
part = Column(Integer, default = 1)
|
||||
available = Column(Boolean, default = True)
|
||||
|
||||
type = ManyToOne('FileType')
|
||||
properties = OneToMany('FileProperty')
|
||||
type_id = Column(Integer, ForeignKey('filetype.id'), index = True)
|
||||
type = relationship('FileType')
|
||||
|
||||
history = OneToMany('RenameHistory')
|
||||
movie = ManyToMany('Movie')
|
||||
release = ManyToMany('Release')
|
||||
library = ManyToMany('Library')
|
||||
properties = relationship('FileProperty')
|
||||
|
||||
movie = relationship('Movie', secondary = movie_files)
|
||||
release = relationship('Release', secondary = release_files)
|
||||
library = relationship('Library', secondary = library_files)
|
||||
|
||||
|
||||
class FileType(Entity):
|
||||
class FileType(Base, TableHelper):
|
||||
__tablename__ = 'filetype'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""Types could be trailer, subtitle, movie, partial movie etc."""
|
||||
|
||||
identifier = Field(String(20), unique = True)
|
||||
type = Field(Unicode(20))
|
||||
name = Field(Unicode(50), nullable = False)
|
||||
identifier = Column(String(20), unique = True)
|
||||
type = Column(Unicode(20))
|
||||
name = Column(Unicode(50), nullable = False)
|
||||
|
||||
files = OneToMany('File')
|
||||
files = relationship('File')
|
||||
|
||||
|
||||
class FileProperty(Entity):
|
||||
class FileProperty(Base, TableHelper):
|
||||
__tablename__ = 'fileproperty'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
"""Properties that can be bound to a file for off-line usage"""
|
||||
|
||||
identifier = Field(String(20), index = True)
|
||||
value = Field(Unicode(255), nullable = False)
|
||||
identifier = Column(String(20), index = True)
|
||||
value = Column(Unicode(255), nullable = False)
|
||||
|
||||
file = ManyToOne('File')
|
||||
file_id = Column(Integer, ForeignKey('file.id'), index = True)
|
||||
file = relationship('File')
|
||||
|
||||
|
||||
class RenameHistory(Entity):
|
||||
"""Remembers from where to where files have been moved."""
|
||||
class Notification(Base, TableHelper):
|
||||
__tablename__ = 'notification'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
old = Field(Unicode(255))
|
||||
new = Field(Unicode(255))
|
||||
""""""
|
||||
|
||||
file = ManyToOne('File')
|
||||
#using_options(order_by = 'added')
|
||||
|
||||
added = Column(Integer, default = lambda: int(time.time()), index = True)
|
||||
read = Column(Boolean, default = False, index = True)
|
||||
message = Column(Unicode(255))
|
||||
data = Column(JsonType)
|
||||
|
||||
|
||||
class Notification(Entity):
|
||||
using_options(order_by = 'added')
|
||||
class Properties(Base, TableHelper):
|
||||
__tablename__ = 'properties'
|
||||
id = Column(Integer, primary_key = True)
|
||||
|
||||
added = Field(Integer, default = lambda: int(time.time()))
|
||||
read = Field(Boolean, default = False)
|
||||
message = Field(Unicode(255))
|
||||
data = Field(JsonType)
|
||||
""""""
|
||||
|
||||
|
||||
class Properties(Entity):
|
||||
|
||||
identifier = Field(String(50), index = True)
|
||||
value = Field(Unicode(255), nullable = False)
|
||||
identifier = Column(String(50), index = True)
|
||||
value = Column(Unicode(255), nullable = False)
|
||||
|
||||
|
||||
def setup():
|
||||
"""Setup the database and create the tables that don't exists yet"""
|
||||
from elixir import setup_all, create_all
|
||||
from couchpotato.environment import Env
|
||||
|
||||
engine = Env.getEngine()
|
||||
|
||||
setup_all()
|
||||
create_all(engine)
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
try:
|
||||
engine.execute("PRAGMA journal_mode = WAL")
|
||||
|
||||
@@ -188,7 +188,7 @@ def runCouchPotato(options, base_path, args, data_dir = None, log_dir = None, En
|
||||
db_exists = os.path.isfile(toUnicode(db_path))
|
||||
|
||||
# Load migrations
|
||||
if db_exists:
|
||||
if False and db_exists:
|
||||
|
||||
from migrate.versioning.api import version_control, db_version, version, upgrade
|
||||
repo = os.path.join(base_path, 'couchpotato', 'core', 'migration')
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
'''
|
||||
Elixir package
|
||||
|
||||
A declarative layer on top of the `SQLAlchemy library
|
||||
<http://www.sqlalchemy.org/>`_. It is a fairly thin wrapper, which provides
|
||||
the ability to create simple Python classes that map directly to relational
|
||||
database tables (this pattern is often referred to as the Active Record design
|
||||
pattern), providing many of the benefits of traditional databases
|
||||
without losing the convenience of Python objects.
|
||||
|
||||
Elixir is intended to replace the ActiveMapper SQLAlchemy extension, and the
|
||||
TurboEntity project but does not intend to replace SQLAlchemy's core features,
|
||||
and instead focuses on providing a simpler syntax for defining model objects
|
||||
when you do not need the full expressiveness of SQLAlchemy's manual mapper
|
||||
definitions.
|
||||
'''
|
||||
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy.types import *
|
||||
|
||||
from elixir.options import using_options, using_table_options, \
|
||||
using_mapper_options, options_defaults, \
|
||||
using_options_defaults
|
||||
from elixir.entity import Entity, EntityBase, EntityMeta, EntityDescriptor, \
|
||||
setup_entities, cleanup_entities
|
||||
from elixir.fields import has_field, Field
|
||||
from elixir.relationships import belongs_to, has_one, has_many, \
|
||||
has_and_belongs_to_many, \
|
||||
ManyToOne, OneToOne, OneToMany, ManyToMany
|
||||
from elixir.properties import has_property, GenericProperty, ColumnProperty, \
|
||||
Synonym
|
||||
from elixir.statements import Statement
|
||||
from elixir.collection import EntityCollection, GlobalEntityCollection
|
||||
|
||||
|
||||
__version__ = '0.8.0dev'
|
||||
|
||||
__all__ = ['Entity', 'EntityBase', 'EntityMeta', 'EntityCollection',
|
||||
'entities',
|
||||
'Field', 'has_field',
|
||||
'has_property', 'GenericProperty', 'ColumnProperty', 'Synonym',
|
||||
'belongs_to', 'has_one', 'has_many', 'has_and_belongs_to_many',
|
||||
'ManyToOne', 'OneToOne', 'OneToMany', 'ManyToMany',
|
||||
'using_options', 'using_table_options', 'using_mapper_options',
|
||||
'options_defaults', 'using_options_defaults',
|
||||
'metadata', 'session',
|
||||
'create_all', 'drop_all',
|
||||
'setup_all', 'cleanup_all',
|
||||
'setup_entities', 'cleanup_entities'] + \
|
||||
sqlalchemy.types.__all__
|
||||
|
||||
__doc_all__ = ['create_all', 'drop_all',
|
||||
'setup_all', 'cleanup_all',
|
||||
'metadata', 'session']
|
||||
|
||||
# default session
|
||||
session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker())
|
||||
|
||||
# default metadata
|
||||
metadata = sqlalchemy.MetaData()
|
||||
|
||||
metadatas = set()
|
||||
|
||||
# default entity collection
|
||||
entities = GlobalEntityCollection()
|
||||
|
||||
|
||||
def create_all(*args, **kwargs):
|
||||
'''Create the necessary tables for all declared entities'''
|
||||
for md in metadatas:
|
||||
md.create_all(*args, **kwargs)
|
||||
|
||||
|
||||
def drop_all(*args, **kwargs):
|
||||
'''Drop tables for all declared entities'''
|
||||
for md in metadatas:
|
||||
md.drop_all(*args, **kwargs)
|
||||
|
||||
|
||||
def setup_all(create_tables=False, *args, **kwargs):
|
||||
'''Setup the table and mapper of all entities in the default entity
|
||||
collection.
|
||||
'''
|
||||
setup_entities(entities)
|
||||
|
||||
# issue the "CREATE" SQL statements
|
||||
if create_tables:
|
||||
create_all(*args, **kwargs)
|
||||
|
||||
|
||||
def cleanup_all(drop_tables=False, *args, **kwargs):
|
||||
'''Clear all mappers, clear the session, and clear all metadatas.
|
||||
Optionally drops the tables.
|
||||
'''
|
||||
session.close()
|
||||
|
||||
cleanup_entities(entities)
|
||||
|
||||
sqlalchemy.orm.clear_mappers()
|
||||
entities.clear()
|
||||
|
||||
if drop_tables:
|
||||
drop_all(*args, **kwargs)
|
||||
|
||||
for md in metadatas:
|
||||
md.clear()
|
||||
metadatas.clear()
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
'''
|
||||
Default entity collection implementation
|
||||
'''
|
||||
import sys
|
||||
import re
|
||||
|
||||
class BaseCollection(list):
|
||||
def __init__(self, entities=None):
|
||||
list.__init__(self)
|
||||
if entities is not None:
|
||||
self.extend(entities)
|
||||
|
||||
def extend(self, entities):
|
||||
for e in entities:
|
||||
self.append(e)
|
||||
|
||||
def clear(self):
|
||||
del self[:]
|
||||
|
||||
def resolve_absolute(self, key, full_path, entity=None, root=None):
|
||||
if root is None:
|
||||
root = entity._descriptor.resolve_root
|
||||
if root:
|
||||
full_path = '%s.%s' % (root, full_path)
|
||||
module_path, classname = full_path.rsplit('.', 1)
|
||||
module = sys.modules[module_path]
|
||||
res = getattr(module, classname, None)
|
||||
if res is None:
|
||||
if entity is not None:
|
||||
raise Exception("Couldn't resolve target '%s' <%s> in '%s'!"
|
||||
% (key, full_path, entity.__name__))
|
||||
else:
|
||||
raise Exception("Couldn't resolve target '%s' <%s>!"
|
||||
% (key, full_path))
|
||||
return res
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self.resolve(key)
|
||||
|
||||
# default entity collection
|
||||
class GlobalEntityCollection(BaseCollection):
|
||||
def __init__(self, entities=None):
|
||||
# _entities is a dict of entities keyed on their name.
|
||||
self._entities = {}
|
||||
super(GlobalEntityCollection, self).__init__(entities)
|
||||
|
||||
def append(self, entity):
|
||||
'''
|
||||
Add an entity to the collection.
|
||||
'''
|
||||
super(EntityCollection, self).append(entity)
|
||||
|
||||
existing_entities = self._entities.setdefault(entity.__name__, [])
|
||||
existing_entities.append(entity)
|
||||
|
||||
def resolve(self, key, entity=None):
|
||||
'''
|
||||
Resolve a key to an Entity. The optional `entity` argument is the
|
||||
"source" entity when resolving relationship targets.
|
||||
'''
|
||||
# Do we have a fully qualified entity name?
|
||||
if '.' in key:
|
||||
return self.resolve_absolute(key, key, entity)
|
||||
else:
|
||||
# Otherwise we look in the entities of this collection
|
||||
res = self._entities.get(key, None)
|
||||
if res is None:
|
||||
if entity:
|
||||
raise Exception("Couldn't resolve target '%s' in '%s'"
|
||||
% (key, entity.__name__))
|
||||
else:
|
||||
raise Exception("This collection does not contain any "
|
||||
"entity corresponding to the key '%s'!"
|
||||
% key)
|
||||
elif len(res) > 1:
|
||||
raise Exception("'%s' resolves to several entities, you should"
|
||||
" use the full path (including the full module"
|
||||
" name) to that entity." % key)
|
||||
else:
|
||||
return res[0]
|
||||
|
||||
def clear(self):
|
||||
self._entities = {}
|
||||
super(GlobalEntityCollection, self).clear()
|
||||
|
||||
# backward compatible name
|
||||
EntityCollection = GlobalEntityCollection
|
||||
|
||||
_leading_dots = re.compile('^([.]*).*$')
|
||||
|
||||
class RelativeEntityCollection(BaseCollection):
|
||||
# the entity=None does not make any sense with a relative entity collection
|
||||
def resolve(self, key, entity):
|
||||
'''
|
||||
Resolve a key to an Entity. The optional `entity` argument is the
|
||||
"source" entity when resolving relationship targets.
|
||||
'''
|
||||
full_path = key
|
||||
|
||||
if '.' not in key or key.startswith('.'):
|
||||
# relative target
|
||||
|
||||
# any leading dot is stripped and with each dot removed,
|
||||
# the entity_module is stripped of one more chunk (starting with
|
||||
# the last one).
|
||||
num_dots = _leading_dots.match(full_path).end(1)
|
||||
full_path = full_path[num_dots:]
|
||||
chunks = entity.__module__.split('.')
|
||||
chunkstokeep = len(chunks) - num_dots
|
||||
if chunkstokeep < 0:
|
||||
raise Exception("Couldn't resolve relative target "
|
||||
"'%s' relative to '%s'" % (key, entity.__module__))
|
||||
entity_module = '.'.join(chunks[:chunkstokeep])
|
||||
|
||||
if entity_module and entity_module is not '__main__':
|
||||
full_path = '%s.%s' % (entity_module, full_path)
|
||||
|
||||
root = ''
|
||||
else:
|
||||
root = None
|
||||
return self.resolve_absolute(key, full_path, entity, root=root)
|
||||
|
||||
def __getattr__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
__all__ = [
|
||||
'before_insert',
|
||||
'after_insert',
|
||||
'before_update',
|
||||
'after_update',
|
||||
'before_delete',
|
||||
'after_delete',
|
||||
'reconstructor'
|
||||
]
|
||||
|
||||
def create_decorator(event_name):
|
||||
def decorator(func):
|
||||
if not hasattr(func, '_elixir_events'):
|
||||
func._elixir_events = []
|
||||
func._elixir_events.append(event_name)
|
||||
return func
|
||||
return decorator
|
||||
|
||||
before_insert = create_decorator('before_insert')
|
||||
after_insert = create_decorator('after_insert')
|
||||
before_update = create_decorator('before_update')
|
||||
after_update = create_decorator('after_update')
|
||||
before_delete = create_decorator('before_delete')
|
||||
after_delete = create_decorator('after_delete')
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
'''
|
||||
Ext package
|
||||
|
||||
Additional Elixir statements and functionality.
|
||||
'''
|
||||
@@ -1,234 +0,0 @@
|
||||
'''
|
||||
Associable Elixir Statement Generator
|
||||
|
||||
==========
|
||||
Associable
|
||||
==========
|
||||
|
||||
About Polymorphic Associations
|
||||
------------------------------
|
||||
|
||||
A frequent pattern in database schemas is the has_and_belongs_to_many, or a
|
||||
many-to-many table. Quite often multiple tables will refer to a single one
|
||||
creating quite a few many-to-many intermediate tables.
|
||||
|
||||
Polymorphic associations lower the amount of many-to-many tables by setting up
|
||||
a table that allows relations to any other table in the database, and relates
|
||||
it to the associable table. In some implementations, this layout does not
|
||||
enforce referential integrity with database foreign key constraints, this
|
||||
implementation uses an additional many-to-many table with foreign key
|
||||
constraints to avoid this problem.
|
||||
|
||||
.. note:
|
||||
SQLite does not support foreign key constraints, so referential integrity
|
||||
can only be enforced using database backends with such support.
|
||||
|
||||
Elixir Statement Generator for Polymorphic Associations
|
||||
-------------------------------------------------------
|
||||
|
||||
The ``associable`` function generates the intermediary tables for an Elixir
|
||||
entity that should be associable with other Elixir entities and returns an
|
||||
Elixir Statement for use with them. This automates the process of creating the
|
||||
polymorphic association tables and ensuring their referential integrity.
|
||||
|
||||
Matching select_XXX and select_by_XXX are also added to the associated entity
|
||||
which allow queries to be run for the associated objects.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Tag(Entity):
|
||||
name = Field(Unicode)
|
||||
|
||||
acts_as_taggable = associable(Tag)
|
||||
|
||||
class Entry(Entity):
|
||||
title = Field(Unicode)
|
||||
acts_as_taggable('tags')
|
||||
|
||||
class Article(Entity):
|
||||
title = Field(Unicode)
|
||||
acts_as_taggable('tags')
|
||||
|
||||
Or if one of the entities being associated should only have a single member of
|
||||
the associated table:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Address(Entity):
|
||||
street = Field(String(130))
|
||||
city = Field(String(100))
|
||||
|
||||
is_addressable = associable(Address, 'addresses')
|
||||
|
||||
class Person(Entity):
|
||||
name = Field(Unicode)
|
||||
orders = OneToMany('Order')
|
||||
is_addressable()
|
||||
|
||||
class Order(Entity):
|
||||
order_num = Field(primary_key=True)
|
||||
item_count = Field(Integer)
|
||||
person = ManyToOne('Person')
|
||||
is_addressable('address', uselist=False)
|
||||
|
||||
home = Address(street='123 Elm St.', city='Spooksville')
|
||||
user = Person(name='Jane Doe')
|
||||
user.addresses.append(home)
|
||||
|
||||
neworder = Order(item_count=4)
|
||||
neworder.address = home
|
||||
user.orders.append(neworder)
|
||||
|
||||
# Queries using the added helpers
|
||||
Person.select_by_addresses(city='Cupertino')
|
||||
Person.select_addresses(and_(Address.c.street=='132 Elm St',
|
||||
Address.c.city=='Smallville'))
|
||||
|
||||
Statement Options
|
||||
-----------------
|
||||
|
||||
The generated Elixir Statement has several options available:
|
||||
|
||||
+---------------+-------------------------------------------------------------+
|
||||
| Option Name | Description |
|
||||
+===============+=============================================================+
|
||||
| ``name`` | Specify a custom name for the Entity attribute. This is |
|
||||
| | used to declare the attribute used to access the associated |
|
||||
| | table values. Otherwise, the name will use the plural_name |
|
||||
| | provided to the associable call. |
|
||||
+---------------+-------------------------------------------------------------+
|
||||
| ``uselist`` | Whether or not the associated table should be represented |
|
||||
| | as a list, or a single property. It should be set to False |
|
||||
| | when the entity should only have a single associated |
|
||||
| | entity. Defaults to True. |
|
||||
+---------------+-------------------------------------------------------------+
|
||||
| ``lazy`` | Determines eager loading of the associated entity objects. |
|
||||
| | Defaults to False, to indicate that they should not be |
|
||||
| | lazily loaded. |
|
||||
+---------------+-------------------------------------------------------------+
|
||||
'''
|
||||
from elixir.statements import Statement
|
||||
import sqlalchemy as sa
|
||||
|
||||
__doc_all__ = ['associable']
|
||||
|
||||
|
||||
def associable(assoc_entity, plural_name=None, lazy=True):
|
||||
'''
|
||||
Generate an associable Elixir Statement
|
||||
'''
|
||||
interface_name = assoc_entity._descriptor.tablename
|
||||
able_name = interface_name + 'able'
|
||||
|
||||
if plural_name:
|
||||
attr_name = "%s_rel" % plural_name
|
||||
else:
|
||||
plural_name = interface_name
|
||||
attr_name = "%s_rel" % interface_name
|
||||
|
||||
class GenericAssoc(object):
|
||||
|
||||
def __init__(self, tablename):
|
||||
self.type = tablename
|
||||
|
||||
#TODO: inherit from entity builder
|
||||
class Associable(object):
|
||||
"""An associable Elixir Statement object"""
|
||||
|
||||
def __init__(self, entity, name=None, uselist=True, lazy=True):
|
||||
self.entity = entity
|
||||
self.lazy = lazy
|
||||
self.uselist = uselist
|
||||
|
||||
if name is None:
|
||||
self.name = plural_name
|
||||
else:
|
||||
self.name = name
|
||||
|
||||
def after_table(self):
|
||||
col = sa.Column('%s_assoc_id' % interface_name, sa.Integer,
|
||||
sa.ForeignKey('%s.id' % able_name))
|
||||
self.entity._descriptor.add_column(col)
|
||||
|
||||
if not hasattr(assoc_entity, '_assoc_table'):
|
||||
metadata = assoc_entity._descriptor.metadata
|
||||
association_table = sa.Table("%s" % able_name, metadata,
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
sa.Column('type', sa.String(40), nullable=False),
|
||||
)
|
||||
tablename = "%s_to_%s" % (able_name, interface_name)
|
||||
association_to_table = sa.Table(tablename, metadata,
|
||||
sa.Column('assoc_id', sa.Integer,
|
||||
sa.ForeignKey(association_table.c.id,
|
||||
ondelete="CASCADE"),
|
||||
primary_key=True),
|
||||
#FIXME: this assumes a single id col
|
||||
sa.Column('%s_id' % interface_name, sa.Integer,
|
||||
sa.ForeignKey(assoc_entity.table.c.id,
|
||||
ondelete="RESTRICT"),
|
||||
primary_key=True),
|
||||
)
|
||||
|
||||
assoc_entity._assoc_table = association_table
|
||||
assoc_entity._assoc_to_table = association_to_table
|
||||
|
||||
def after_mapper(self):
|
||||
if not hasattr(assoc_entity, '_assoc_mapper'):
|
||||
assoc_entity._assoc_mapper = sa.orm.mapper(
|
||||
GenericAssoc, assoc_entity._assoc_table, properties={
|
||||
'targets': sa.orm.relation(
|
||||
assoc_entity,
|
||||
secondary=assoc_entity._assoc_to_table,
|
||||
lazy=lazy, backref='associations',
|
||||
order_by=assoc_entity.mapper.order_by)
|
||||
})
|
||||
|
||||
entity = self.entity
|
||||
entity.mapper.add_property(
|
||||
attr_name,
|
||||
sa.orm.relation(GenericAssoc, lazy=self.lazy,
|
||||
backref='_backref_%s' % entity.table.name)
|
||||
)
|
||||
|
||||
if self.uselist:
|
||||
def get(self):
|
||||
if getattr(self, attr_name) is None:
|
||||
setattr(self, attr_name,
|
||||
GenericAssoc(entity.table.name))
|
||||
return getattr(self, attr_name).targets
|
||||
setattr(entity, self.name, property(get))
|
||||
else:
|
||||
# scalar based property decorator
|
||||
def get(self):
|
||||
attr = getattr(self, attr_name)
|
||||
if attr is not None:
|
||||
return attr.targets[0]
|
||||
else:
|
||||
return None
|
||||
def set(self, value):
|
||||
if getattr(self, attr_name) is None:
|
||||
setattr(self, attr_name,
|
||||
GenericAssoc(entity.table.name))
|
||||
getattr(self, attr_name).targets = [value]
|
||||
setattr(entity, self.name, property(get, set))
|
||||
|
||||
# self.name is both set via mapper synonym and the python
|
||||
# property, but that's how synonym properties work.
|
||||
# adding synonym property after "real" property otherwise it
|
||||
# breaks when using SQLAlchemy > 0.4.1
|
||||
entity.mapper.add_property(self.name, sa.orm.synonym(attr_name))
|
||||
|
||||
# add helper methods
|
||||
def select_by(cls, **kwargs):
|
||||
return cls.query.join(attr_name, 'targets') \
|
||||
.filter_by(**kwargs).all()
|
||||
setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))
|
||||
|
||||
def select(cls, *args, **kwargs):
|
||||
return cls.query.join(attr_name, 'targets') \
|
||||
.filter(*args, **kwargs).all()
|
||||
setattr(entity, 'select_%s' % self.name, classmethod(select))
|
||||
|
||||
return Statement(Associable)
|
||||
@@ -1,124 +0,0 @@
|
||||
'''
|
||||
An encryption plugin for Elixir utilizing the excellent PyCrypto library, which
|
||||
can be downloaded here: http://www.amk.ca/python/code/crypto
|
||||
|
||||
Values for columns that are specified to be encrypted will be transparently
|
||||
encrypted and safely encoded for storage in a unicode column using the powerful
|
||||
and secure Blowfish Cipher using a specified "secret" which can be passed into
|
||||
the plugin at class declaration time.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
from elixir import *
|
||||
from elixir.ext.encrypted import acts_as_encrypted
|
||||
|
||||
class Person(Entity):
|
||||
name = Field(Unicode)
|
||||
password = Field(Unicode)
|
||||
ssn = Field(Unicode)
|
||||
acts_as_encrypted(for_fields=['password', 'ssn'],
|
||||
with_secret='secret')
|
||||
|
||||
The above Person entity will automatically encrypt and decrypt the password and
|
||||
ssn columns on save, update, and load. Different secrets can be specified on
|
||||
an entity by entity basis, for added security.
|
||||
|
||||
**Important note**: instance attributes are encrypted in-place. This means that
|
||||
if one of the encrypted attributes of an instance is accessed after the
|
||||
instance has been flushed to the database (and thus encrypted), the value for
|
||||
that attribute will be crypted in the in-memory object in addition to the
|
||||
database row.
|
||||
'''
|
||||
|
||||
from Crypto.Cipher import Blowfish
|
||||
from elixir.statements import Statement
|
||||
from sqlalchemy.orm import MapperExtension, EXT_CONTINUE, EXT_STOP
|
||||
|
||||
try:
|
||||
from sqlalchemy.orm import EXT_PASS
|
||||
SA05orlater = False
|
||||
except ImportError:
|
||||
SA05orlater = True
|
||||
|
||||
__all__ = ['acts_as_encrypted']
|
||||
__doc_all__ = []
|
||||
|
||||
|
||||
#
|
||||
# encryption and decryption functions
|
||||
#
|
||||
|
||||
def encrypt_value(value, secret):
|
||||
return Blowfish.new(secret, Blowfish.MODE_CFB) \
|
||||
.encrypt(value).encode('string_escape')
|
||||
|
||||
def decrypt_value(value, secret):
|
||||
return Blowfish.new(secret, Blowfish.MODE_CFB) \
|
||||
.decrypt(value.decode('string_escape'))
|
||||
|
||||
|
||||
#
|
||||
# acts_as_encrypted statement
|
||||
#
|
||||
|
||||
class ActsAsEncrypted(object):
|
||||
|
||||
def __init__(self, entity, for_fields=[], with_secret='abcdef'):
|
||||
|
||||
def perform_encryption(instance, encrypt=True):
|
||||
encrypted = getattr(instance, '_elixir_encrypted', None)
|
||||
if encrypted is encrypt:
|
||||
# skipping encryption or decryption, as it is already done
|
||||
return
|
||||
else:
|
||||
# marking instance as already encrypted/decrypted
|
||||
instance._elixir_encrypted = encrypt
|
||||
|
||||
if encrypt:
|
||||
func = encrypt_value
|
||||
else:
|
||||
func = decrypt_value
|
||||
|
||||
for column_name in for_fields:
|
||||
current_value = getattr(instance, column_name)
|
||||
if current_value:
|
||||
setattr(instance, column_name,
|
||||
func(current_value, with_secret))
|
||||
|
||||
def perform_decryption(instance):
|
||||
perform_encryption(instance, encrypt=False)
|
||||
|
||||
class EncryptedMapperExtension(MapperExtension):
|
||||
|
||||
def before_insert(self, mapper, connection, instance):
|
||||
perform_encryption(instance)
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_update(self, mapper, connection, instance):
|
||||
perform_encryption(instance)
|
||||
return EXT_CONTINUE
|
||||
|
||||
if SA05orlater:
|
||||
def reconstruct_instance(self, mapper, instance):
|
||||
perform_decryption(instance)
|
||||
# no special return value is required for
|
||||
# reconstruct_instance, but you never know...
|
||||
return EXT_CONTINUE
|
||||
else:
|
||||
def populate_instance(self, mapper, selectcontext, row,
|
||||
instance, *args, **kwargs):
|
||||
mapper.populate_instance(selectcontext, instance, row,
|
||||
*args, **kwargs)
|
||||
perform_decryption(instance)
|
||||
# EXT_STOP because we already did populate the instance and
|
||||
# the normal processing should not happen
|
||||
return EXT_STOP
|
||||
|
||||
# make sure that the entity's mapper has our mapper extension
|
||||
entity._descriptor.add_mapper_extension(EncryptedMapperExtension())
|
||||
|
||||
|
||||
acts_as_encrypted = Statement(ActsAsEncrypted)
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
'''
|
||||
DDL statements for Elixir.
|
||||
|
||||
Entities having the perform_ddl statement, will automatically execute the
|
||||
given DDL statement, at the given moment: ether before or after the table
|
||||
creation in SQL.
|
||||
|
||||
The 'when' argument can be either 'before-create' or 'after-create'.
|
||||
The 'statement' argument can be one of:
|
||||
|
||||
- a single string statement
|
||||
- a list of string statements, in which case, each of them will be executed
|
||||
in turn.
|
||||
- a callable which should take no argument and return either a single string
|
||||
or a list of strings.
|
||||
|
||||
In each string statement, you may use the special '%(fullname)s' construct,
|
||||
that will be replaced with the real table name including schema, if unknown
|
||||
to you. Also, self explained '%(table)s' and '%(schema)s' may be used here.
|
||||
|
||||
You would use this extension to handle non elixir sql statemts, like triggers
|
||||
etc.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Movie(Entity):
|
||||
title = Field(Unicode(30), primary_key=True)
|
||||
year = Field(Integer)
|
||||
|
||||
perform_ddl('after-create',
|
||||
"insert into %(fullname)s values ('Alien', 1979)")
|
||||
|
||||
preload_data is a more specific statement meant to preload data in your
|
||||
entity table from a list of tuples (of fields values for each row).
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Movie(Entity):
|
||||
title = Field(Unicode(30), primary_key=True)
|
||||
year = Field(Integer)
|
||||
|
||||
preload_data(('title', 'year'),
|
||||
[(u'Alien', 1979), (u'Star Wars', 1977)])
|
||||
preload_data(('year', 'title'),
|
||||
[(1982, u'Blade Runner')])
|
||||
preload_data(data=[(u'Batman', 1966)])
|
||||
'''
|
||||
|
||||
from elixir.statements import Statement
|
||||
from elixir.properties import EntityBuilder
|
||||
from sqlalchemy import DDL
|
||||
|
||||
__all__ = ['perform_ddl', 'preload_data']
|
||||
__doc_all__ = []
|
||||
|
||||
#
|
||||
# the perform_ddl statement
|
||||
#
|
||||
class PerformDDLEntityBuilder(EntityBuilder):
|
||||
|
||||
def __init__(self, entity, when, statement, on=None, context=None):
|
||||
self.entity = entity
|
||||
self.when = when
|
||||
self.statement = statement
|
||||
self.on = on
|
||||
self.context = context
|
||||
|
||||
def after_table(self):
|
||||
statement = self.statement
|
||||
if hasattr(statement, '__call__'):
|
||||
statement = statement()
|
||||
if not isinstance(statement, list):
|
||||
statement = [statement]
|
||||
for s in statement:
|
||||
ddl = DDL(s, self.on, self.context)
|
||||
ddl.execute_at(self.when, self.entity.table)
|
||||
|
||||
perform_ddl = Statement(PerformDDLEntityBuilder)
|
||||
|
||||
#
|
||||
# the preload_data statement
|
||||
#
|
||||
class PreloadDataEntityBuilder(EntityBuilder):
|
||||
|
||||
def __init__(self, entity, columns=None, data=None):
|
||||
self.entity = entity
|
||||
self.columns = columns
|
||||
self.data = data
|
||||
|
||||
def after_table(self):
|
||||
all_columns = [col.name for col in self.entity.table.columns]
|
||||
def onload(event, schema_item, connection):
|
||||
columns = self.columns
|
||||
if columns is None:
|
||||
columns = all_columns
|
||||
data = self.data
|
||||
if hasattr(data, '__call__'):
|
||||
data = data()
|
||||
insert = schema_item.insert()
|
||||
connection.execute(insert,
|
||||
[dict(zip(columns, values)) for values in data])
|
||||
|
||||
self.entity.table.append_ddl_listener('after-create', onload)
|
||||
|
||||
preload_data = Statement(PreloadDataEntityBuilder)
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
'''
|
||||
A versioning plugin for Elixir.
|
||||
|
||||
Entities that are marked as versioned with the `acts_as_versioned` statement
|
||||
will automatically have a history table created and a timestamp and version
|
||||
column added to their tables. In addition, versioned entities are provided
|
||||
with four new methods: revert, revert_to, compare_with and get_as_of, and one
|
||||
new attribute: versions. Entities with compound primary keys are supported.
|
||||
|
||||
The `versions` attribute will contain a list of previous versions of the
|
||||
instance, in increasing version number order.
|
||||
|
||||
The `get_as_of` method will retrieve a previous version of the instance "as of"
|
||||
a specified datetime. If the current version is the most recent, it will be
|
||||
returned.
|
||||
|
||||
The `revert` method will rollback the current instance to its previous version,
|
||||
if possible. Once reverted, the current instance will be expired from the
|
||||
session, and you will need to fetch it again to retrieve the now reverted
|
||||
instance.
|
||||
|
||||
The `revert_to` method will rollback the current instance to the specified
|
||||
version number, if possibe. Once reverted, the current instance will be expired
|
||||
from the session, and you will need to fetch it again to retrieve the now
|
||||
reverted instance.
|
||||
|
||||
The `compare_with` method will compare the instance with a previous version. A
|
||||
dictionary will be returned with each field difference as an element in the
|
||||
dictionary where the key is the field name and the value is a tuple of the
|
||||
format (current_value, version_value). Version instances also have a
|
||||
`compare_with` method so that two versions can be compared.
|
||||
|
||||
Also included in the module is a `after_revert` decorator that can be used to
|
||||
decorate methods on the versioned entity that will be called following that
|
||||
instance being reverted.
|
||||
|
||||
The acts_as_versioned statement also accepts an optional `ignore` argument
|
||||
that consists of a list of strings, specifying names of fields. Changes in
|
||||
those fields will not result in a version increment. In addition, you can
|
||||
pass in an optional `check_concurrent` argument, which will use SQLAlchemy's
|
||||
built-in optimistic concurrency mechanisms.
|
||||
|
||||
Note that relationships that are stored in mapping tables will not be included
|
||||
as part of the versioning process, and will need to be handled manually. Only
|
||||
values within the entity's main table will be versioned into the history table.
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
import inspect
|
||||
|
||||
from sqlalchemy import Table, Column, and_, desc
|
||||
from sqlalchemy.orm import mapper, MapperExtension, EXT_CONTINUE, \
|
||||
object_session
|
||||
|
||||
from elixir import Integer, DateTime
|
||||
from elixir.statements import Statement
|
||||
from elixir.properties import EntityBuilder
|
||||
from elixir.entity import getmembers
|
||||
|
||||
__all__ = ['acts_as_versioned', 'after_revert']
|
||||
__doc_all__ = []
|
||||
|
||||
#
|
||||
# utility functions
|
||||
#
|
||||
|
||||
def get_entity_where(instance):
|
||||
clauses = []
|
||||
for column in instance.table.primary_key.columns:
|
||||
instance_value = getattr(instance, column.name)
|
||||
clauses.append(column==instance_value)
|
||||
return and_(*clauses)
|
||||
|
||||
|
||||
def get_history_where(instance):
|
||||
clauses = []
|
||||
history_columns = instance.__history_table__.primary_key.columns
|
||||
for column in instance.table.primary_key.columns:
|
||||
instance_value = getattr(instance, column.name)
|
||||
history_column = getattr(history_columns, column.name)
|
||||
clauses.append(history_column==instance_value)
|
||||
return and_(*clauses)
|
||||
|
||||
|
||||
#
|
||||
# a mapper extension to track versions on insert, update, and delete
|
||||
#
|
||||
|
||||
class VersionedMapperExtension(MapperExtension):
|
||||
def before_insert(self, mapper, connection, instance):
|
||||
version_colname, timestamp_colname = \
|
||||
instance.__class__.__versioned_column_names__
|
||||
setattr(instance, version_colname, 1)
|
||||
setattr(instance, timestamp_colname, datetime.now())
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_update(self, mapper, connection, instance):
|
||||
old_values = instance.table.select(get_entity_where(instance)) \
|
||||
.execute().fetchone()
|
||||
|
||||
# SA might've flagged this for an update even though it didn't change.
|
||||
# This occurs when a relation is updated, thus marking this instance
|
||||
# for a save/update operation. We check here against the last version
|
||||
# to ensure we really should save this version and update the version
|
||||
# data.
|
||||
ignored = instance.__class__.__ignored_fields__
|
||||
version_colname, timestamp_colname = \
|
||||
instance.__class__.__versioned_column_names__
|
||||
for key in instance.table.c.keys():
|
||||
if key in ignored:
|
||||
continue
|
||||
if getattr(instance, key) != old_values[key]:
|
||||
# the instance was really updated, so we create a new version
|
||||
dict_values = dict(old_values.items())
|
||||
connection.execute(
|
||||
instance.__class__.__history_table__.insert(), dict_values)
|
||||
old_version = getattr(instance, version_colname)
|
||||
setattr(instance, version_colname, old_version + 1)
|
||||
setattr(instance, timestamp_colname, datetime.now())
|
||||
break
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_delete(self, mapper, connection, instance):
|
||||
connection.execute(instance.__history_table__.delete(
|
||||
get_history_where(instance)
|
||||
))
|
||||
return EXT_CONTINUE
|
||||
|
||||
|
||||
versioned_mapper_extension = VersionedMapperExtension()
|
||||
|
||||
|
||||
#
|
||||
# the acts_as_versioned statement
|
||||
#
|
||||
|
||||
class VersionedEntityBuilder(EntityBuilder):
|
||||
|
||||
def __init__(self, entity, ignore=None, check_concurrent=False,
|
||||
column_names=None):
|
||||
self.entity = entity
|
||||
self.add_mapper_extension(versioned_mapper_extension)
|
||||
#TODO: we should rather check that the version_id_col isn't set
|
||||
# externally
|
||||
self.check_concurrent = check_concurrent
|
||||
|
||||
# Changes in these fields will be ignored
|
||||
if column_names is None:
|
||||
column_names = ['version', 'timestamp']
|
||||
entity.__versioned_column_names__ = column_names
|
||||
if ignore is None:
|
||||
ignore = []
|
||||
ignore.extend(column_names)
|
||||
entity.__ignored_fields__ = ignore
|
||||
|
||||
def create_non_pk_cols(self):
|
||||
# add a version column to the entity, along with a timestamp
|
||||
version_colname, timestamp_colname = \
|
||||
self.entity.__versioned_column_names__
|
||||
#XXX: fail in case the columns already exist?
|
||||
#col_names = [col.name for col in self.entity._descriptor.columns]
|
||||
#if version_colname not in col_names:
|
||||
self.add_table_column(Column(version_colname, Integer))
|
||||
#if timestamp_colname not in col_names:
|
||||
self.add_table_column(Column(timestamp_colname, DateTime))
|
||||
|
||||
# add a concurrent_version column to the entity, if required
|
||||
if self.check_concurrent:
|
||||
self.entity._descriptor.version_id_col = 'concurrent_version'
|
||||
|
||||
# we copy columns from the main entity table, so we need it to exist first
|
||||
def after_table(self):
|
||||
entity = self.entity
|
||||
version_colname, timestamp_colname = \
|
||||
entity.__versioned_column_names__
|
||||
|
||||
# look for events
|
||||
after_revert_events = []
|
||||
for name, func in getmembers(entity, inspect.ismethod):
|
||||
if getattr(func, '_elixir_after_revert', False):
|
||||
after_revert_events.append(func)
|
||||
|
||||
# create a history table for the entity
|
||||
skipped_columns = [version_colname]
|
||||
if self.check_concurrent:
|
||||
skipped_columns.append('concurrent_version')
|
||||
|
||||
columns = [
|
||||
column.copy() for column in entity.table.c
|
||||
if column.name not in skipped_columns
|
||||
]
|
||||
columns.append(Column(version_colname, Integer, primary_key=True))
|
||||
table = Table(entity.table.name + '_history', entity.table.metadata,
|
||||
*columns
|
||||
)
|
||||
entity.__history_table__ = table
|
||||
|
||||
# create an object that represents a version of this entity
|
||||
class Version(object):
|
||||
pass
|
||||
|
||||
# map the version class to the history table for this entity
|
||||
Version.__name__ = entity.__name__ + 'Version'
|
||||
Version.__versioned_entity__ = entity
|
||||
mapper(Version, entity.__history_table__)
|
||||
|
||||
version_col = getattr(table.c, version_colname)
|
||||
timestamp_col = getattr(table.c, timestamp_colname)
|
||||
|
||||
# attach utility methods and properties to the entity
|
||||
def get_versions(self):
|
||||
v = object_session(self).query(Version) \
|
||||
.filter(get_history_where(self)) \
|
||||
.order_by(version_col) \
|
||||
.all()
|
||||
# history contains all the previous records.
|
||||
# Add the current one to the list to get all the versions
|
||||
v.append(self)
|
||||
return v
|
||||
|
||||
def get_as_of(self, dt):
|
||||
# if the passed in timestamp is older than our current version's
|
||||
# time stamp, then the most recent version is our current version
|
||||
if getattr(self, timestamp_colname) < dt:
|
||||
return self
|
||||
|
||||
# otherwise, we need to look to the history table to get our
|
||||
# older version
|
||||
sess = object_session(self)
|
||||
query = sess.query(Version) \
|
||||
.filter(and_(get_history_where(self),
|
||||
timestamp_col <= dt)) \
|
||||
.order_by(desc(timestamp_col)).limit(1)
|
||||
return query.first()
|
||||
|
||||
def revert_to(self, to_version):
|
||||
if isinstance(to_version, Version):
|
||||
to_version = getattr(to_version, version_colname)
|
||||
|
||||
old_version = table.select(and_(
|
||||
get_history_where(self),
|
||||
version_col == to_version
|
||||
)).execute().fetchone()
|
||||
|
||||
entity.table.update(get_entity_where(self)).execute(
|
||||
dict(old_version.items())
|
||||
)
|
||||
|
||||
table.delete(and_(get_history_where(self),
|
||||
version_col >= to_version)).execute()
|
||||
self.expire()
|
||||
for event in after_revert_events:
|
||||
event(self)
|
||||
|
||||
def revert(self):
|
||||
assert getattr(self, version_colname) > 1
|
||||
self.revert_to(getattr(self, version_colname) - 1)
|
||||
|
||||
def compare_with(self, version):
|
||||
differences = {}
|
||||
for column in self.table.c:
|
||||
if column.name in (version_colname, 'concurrent_version'):
|
||||
continue
|
||||
this = getattr(self, column.name)
|
||||
that = getattr(version, column.name)
|
||||
if this != that:
|
||||
differences[column.name] = (this, that)
|
||||
return differences
|
||||
|
||||
entity.versions = property(get_versions)
|
||||
entity.get_as_of = get_as_of
|
||||
entity.revert_to = revert_to
|
||||
entity.revert = revert
|
||||
entity.compare_with = compare_with
|
||||
Version.compare_with = compare_with
|
||||
|
||||
acts_as_versioned = Statement(VersionedEntityBuilder)
|
||||
|
||||
|
||||
def after_revert(func):
|
||||
"""
|
||||
Decorator for watching for revert events.
|
||||
"""
|
||||
func._elixir_after_revert = True
|
||||
return func
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
'''
|
||||
This module provides support for defining the fields (columns) of your
|
||||
entities. Elixir currently supports two syntaxes to do so: the default
|
||||
`Attribute-based syntax`_ as well as the has_field_ DSL statement.
|
||||
|
||||
Attribute-based syntax
|
||||
----------------------
|
||||
|
||||
Here is a quick example of how to use the object-oriented syntax.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Person(Entity):
|
||||
id = Field(Integer, primary_key=True)
|
||||
name = Field(String(50), required=True)
|
||||
ssn = Field(String(50), unique=True)
|
||||
biography = Field(Text)
|
||||
join_date = Field(DateTime, default=datetime.datetime.now)
|
||||
photo = Field(Binary, deferred=True)
|
||||
_email = Field(String(20), colname='email', synonym='email')
|
||||
|
||||
def _set_email(self, email):
|
||||
self._email = email
|
||||
def _get_email(self):
|
||||
return self._email
|
||||
email = property(_get_email, _set_email)
|
||||
|
||||
|
||||
The Field class takes one mandatory argument, which is its type. Please refer
|
||||
to SQLAlchemy documentation for a list of `types supported by SQLAlchemy
|
||||
<http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/types.html>`_.
|
||||
|
||||
Following that first mandatory argument, fields can take any number of
|
||||
optional keyword arguments. Please note that all the **arguments** that are
|
||||
**not specifically processed by Elixir**, as mentioned in the documentation
|
||||
below **are passed on to the SQLAlchemy ``Column`` object**. Please refer to
|
||||
the `SQLAlchemy Column object's documentation
|
||||
<http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/schema.html
|
||||
#sqlalchemy.schema.Column>`_ for more details about other
|
||||
supported keyword arguments.
|
||||
|
||||
The following Elixir-specific arguments are supported:
|
||||
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| Argument Name | Description |
|
||||
+===================+=========================================================+
|
||||
| ``required`` | Specify whether or not this field can be set to None |
|
||||
| | (left without a value). Defaults to ``False``, unless |
|
||||
| | the field is a primary key. |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| ``colname`` | Specify a custom name for the column of this field. By |
|
||||
| | default the column will have the same name as the |
|
||||
| | attribute. |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| ``deferred`` | Specify whether this particular column should be |
|
||||
| | fetched by default (along with the other columns) when |
|
||||
| | an instance of the entity is fetched from the database |
|
||||
| | or rather only later on when this particular column is |
|
||||
| | first referenced. This can be useful when one wants to |
|
||||
| | avoid loading a large text or binary field into memory |
|
||||
| | when its not needed. Individual columns can be lazy |
|
||||
| | loaded by themselves (by using ``deferred=True``) |
|
||||
| | or placed into groups that lazy-load together (by using |
|
||||
| | ``deferred`` = `"group_name"`). |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| ``synonym`` | Specify a synonym name for this field. The field will |
|
||||
| | also be usable under that name in keyword-based Query |
|
||||
| | functions such as filter_by. The Synonym class (see the |
|
||||
| | `properties` module) provides a similar functionality |
|
||||
| | with an (arguably) nicer syntax, but a limited scope. |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
|
||||
has_field
|
||||
---------
|
||||
|
||||
The `has_field` statement allows you to define fields one at a time.
|
||||
|
||||
The first argument is the name of the field, the second is its type. Following
|
||||
these, any number of keyword arguments can be specified for additional
|
||||
behavior. The following arguments are supported:
|
||||
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| Argument Name | Description |
|
||||
+===================+=========================================================+
|
||||
| ``through`` | Specify a relation name to go through. This field will |
|
||||
| | not exist as a column on the database but will be a |
|
||||
| | property which automatically proxy values to the |
|
||||
| | ``attribute`` attribute of the object pointed to by the |
|
||||
| | relation. If the ``attribute`` argument is not present, |
|
||||
| | the name of the current field will be used. In an |
|
||||
| | has_field statement, you can only proxy through a |
|
||||
| | belongs_to or an has_one relationship. |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
| ``attribute`` | Name of the "endpoint" attribute to proxy to. This |
|
||||
| | should only be used in combination with the ``through`` |
|
||||
| | argument. |
|
||||
+-------------------+---------------------------------------------------------+
|
||||
|
||||
|
||||
Here is a quick example of how to use ``has_field``.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Person(Entity):
|
||||
has_field('id', Integer, primary_key=True)
|
||||
has_field('name', String(50))
|
||||
'''
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy.orm import deferred, synonym
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
|
||||
from elixir.statements import ClassMutator
|
||||
from elixir.properties import Property
|
||||
|
||||
__doc_all__ = ['Field']
|
||||
|
||||
|
||||
class Field(Property):
|
||||
'''
|
||||
Represents the definition of a 'field' on an entity.
|
||||
|
||||
This class represents a column on the table where the entity is stored.
|
||||
'''
|
||||
|
||||
def __init__(self, type, *args, **kwargs):
|
||||
super(Field, self).__init__()
|
||||
|
||||
self.colname = kwargs.pop('colname', None)
|
||||
self.synonym = kwargs.pop('synonym', None)
|
||||
self.deferred = kwargs.pop('deferred', False)
|
||||
if 'required' in kwargs:
|
||||
kwargs['nullable'] = not kwargs.pop('required')
|
||||
self.type = type
|
||||
self.primary_key = kwargs.get('primary_key', False)
|
||||
|
||||
self.column = None
|
||||
self.property = None
|
||||
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def attach(self, entity, name):
|
||||
# If no colname was defined (through the 'colname' kwarg), set
|
||||
# it to the name of the attr.
|
||||
if self.colname is None:
|
||||
self.colname = name
|
||||
super(Field, self).attach(entity, name)
|
||||
|
||||
def create_pk_cols(self):
|
||||
if self.primary_key:
|
||||
self.create_col()
|
||||
|
||||
def create_non_pk_cols(self):
|
||||
if not self.primary_key:
|
||||
self.create_col()
|
||||
|
||||
def create_col(self):
|
||||
self.column = Column(self.colname, self.type,
|
||||
*self.args, **self.kwargs)
|
||||
self.add_table_column(self.column)
|
||||
|
||||
def create_properties(self):
|
||||
if self.deferred:
|
||||
group = None
|
||||
if isinstance(self.deferred, basestring):
|
||||
group = self.deferred
|
||||
self.property = deferred(self.column, group=group)
|
||||
elif self.name != self.colname:
|
||||
# if the property name is different from the column name, we need
|
||||
# to add an explicit property (otherwise nothing is needed as it's
|
||||
# done automatically by SA)
|
||||
self.property = self.column
|
||||
|
||||
if self.property is not None:
|
||||
self.add_mapper_property(self.name, self.property)
|
||||
|
||||
if self.synonym:
|
||||
self.add_mapper_property(self.synonym, synonym(self.name))
|
||||
|
||||
|
||||
def has_field_handler(entity, name, *args, **kwargs):
|
||||
if 'through' in kwargs:
|
||||
setattr(entity, name,
|
||||
association_proxy(kwargs.pop('through'),
|
||||
kwargs.pop('attribute', name),
|
||||
**kwargs))
|
||||
return
|
||||
field = Field(*args, **kwargs)
|
||||
field.attach(entity, name)
|
||||
|
||||
has_field = ClassMutator(has_field_handler)
|
||||
@@ -1,274 +0,0 @@
|
||||
'''
|
||||
This module provides support for defining several options on your Elixir
|
||||
entities. There are three different kinds of options that can be set
|
||||
up, and for this there are three different statements: using_options_,
|
||||
using_table_options_ and using_mapper_options_.
|
||||
|
||||
Alternatively, these options can be set on all Elixir entities by modifying
|
||||
the `options_defaults` dictionary before defining any entity.
|
||||
|
||||
`using_options`
|
||||
---------------
|
||||
The 'using_options' DSL statement allows you to set up some additional
|
||||
behaviors on your model objects, including table names, ordering, and
|
||||
more. To specify an option, simply supply the option as a keyword
|
||||
argument onto the statement, as follows:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Person(Entity):
|
||||
name = Field(Unicode(64))
|
||||
|
||||
using_options(shortnames=True, order_by='name')
|
||||
|
||||
The list of supported arguments are as follows:
|
||||
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| Option Name | Description |
|
||||
+=====================+=======================================================+
|
||||
| ``inheritance`` | Specify the type of inheritance this entity must use. |
|
||||
| | It can be one of ``single``, ``concrete`` or |
|
||||
| | ``multi``. Defaults to ``single``. |
|
||||
| | Note that polymorphic concrete inheritance is |
|
||||
| | currently not implemented. See: |
|
||||
| | http://www.sqlalchemy.org/docs/05/mappers.html |
|
||||
| | #mapping-class-inheritance-hierarchies for an |
|
||||
| | explanation of the different kinds of inheritances. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``abstract`` | Set 'abstract'=True to declare abstract entity. |
|
||||
| | Abstract base classes are useful when you want to put |
|
||||
| | some common information into a number of other |
|
||||
| | entities. Abstract entity will not be used to create |
|
||||
| | any database table. Instead, when it is used as a base|
|
||||
| | class for other entity, its fields will be added to |
|
||||
| | those of the child class. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``polymorphic`` | Whether the inheritance should be polymorphic or not. |
|
||||
| | Defaults to ``True``. The column used to store the |
|
||||
| | type of each row is named "row_type" by default. You |
|
||||
| | can change this by passing the desired name for the |
|
||||
| | column to this argument. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``identity`` | Specify a custom polymorphic identity. When using |
|
||||
| | polymorphic inheritance, this value (usually a |
|
||||
| | string) will represent this particular entity (class) |
|
||||
| | . It will be used to differentiate it from other |
|
||||
| | entities (classes) in your inheritance hierarchy when |
|
||||
| | loading from the database instances of different |
|
||||
| | entities in that hierarchy at the same time. |
|
||||
| | This value will be stored by default in the |
|
||||
| | "row_type" column of the entity's table (see above). |
|
||||
| | You can either provide a |
|
||||
| | plain string or a callable. The callable will be |
|
||||
| | given the entity (ie class) as argument and must |
|
||||
| | return a value (usually a string) representing the |
|
||||
| | polymorphic identity of that entity. |
|
||||
| | By default, this value is automatically generated: it |
|
||||
| | is the name of the entity lower-cased. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``metadata`` | Specify a custom MetaData for this entity. |
|
||||
| | By default, entities uses the global |
|
||||
| | ``elixir.metadata``. |
|
||||
| | This option can also be set for all entities of a |
|
||||
| | module by setting the ``__metadata__`` attribute of |
|
||||
| | that module. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``autoload`` | Automatically load column definitions from the |
|
||||
| | existing database table. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``tablename`` | Specify a custom tablename. You can either provide a |
|
||||
| | plain string or a callable. The callable will be |
|
||||
| | given the entity (ie class) as argument and must |
|
||||
| | return a string representing the name of the table |
|
||||
| | for that entity. By default, the tablename is |
|
||||
| | automatically generated: it is a concatenation of the |
|
||||
| | full module-path to the entity and the entity (class) |
|
||||
| | name itself. The result is lower-cased and separated |
|
||||
| | by underscores ("_"), eg.: for an entity named |
|
||||
| | "MyEntity" in the module "project1.model", the |
|
||||
| | generated table name will be |
|
||||
| | "project1_model_myentity". |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``shortnames`` | Specify whether or not the automatically generated |
|
||||
| | table names include the full module-path |
|
||||
| | to the entity. If ``shortnames`` is ``True``, only |
|
||||
| | the entity name is used. Defaults to ``False``. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``auto_primarykey`` | If given as string, it will represent the |
|
||||
| | auto-primary-key's column name. If this option |
|
||||
| | is True, it will allow auto-creation of a primary |
|
||||
| | key if there's no primary key defined for the |
|
||||
| | corresponding entity. If this option is False, |
|
||||
| | it will disallow auto-creation of a primary key. |
|
||||
| | Defaults to ``True``. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``version_id_col`` | If this option is True, it will create a version |
|
||||
| | column automatically using the default name. If given |
|
||||
| | as string, it will create the column using that name. |
|
||||
| | This can be used to prevent concurrent modifications |
|
||||
| | to the entity's table rows (i.e. it will raise an |
|
||||
| | exception if it happens). Defaults to ``False``. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``order_by`` | How to order select results. Either a string or a |
|
||||
| | list of strings, composed of the field name, |
|
||||
| | optionally lead by a minus (for descending order). |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``session`` | Specify a custom contextual session for this entity. |
|
||||
| | By default, entities uses the global |
|
||||
| | ``elixir.session``. |
|
||||
| | This option takes a ``ScopedSession`` object or |
|
||||
| | ``None``. In the later case your entity will be |
|
||||
| | mapped using a non-contextual mapper which requires |
|
||||
| | manual session management, as seen in pure SQLAlchemy.|
|
||||
| | This option can also be set for all entities of a |
|
||||
| | module by setting the ``__session__`` attribute of |
|
||||
| | that module. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
| ``allowcoloverride``| Specify whether it is allowed to override columns. |
|
||||
| | By default, Elixir forbids you to add a column to an |
|
||||
| | entity's table which already exist in that table. If |
|
||||
| | you set this option to ``True`` it will skip that |
|
||||
| | check. Use with care as it is easy to shoot oneself |
|
||||
| | in the foot when overriding columns. |
|
||||
+---------------------+-------------------------------------------------------+
|
||||
|
||||
For examples, please refer to the examples and unit tests.
|
||||
|
||||
`using_table_options`
|
||||
---------------------
|
||||
The 'using_table_options' DSL statement allows you to set up some
|
||||
additional options on your entity table. It is meant only to handle the
|
||||
options which are not supported directly by the 'using_options' statement.
|
||||
By opposition to the 'using_options' statement, these options are passed
|
||||
directly to the underlying SQLAlchemy Table object (both non-keyword arguments
|
||||
and keyword arguments) without any processing.
|
||||
|
||||
For further information, please refer to the `SQLAlchemy table's documentation
|
||||
<http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/schema.html
|
||||
#sqlalchemy.schema.Table>`_.
|
||||
|
||||
You might also be interested in the section about `constraints
|
||||
<http://www.sqlalchemy.org/docs/05/metadata.html
|
||||
#defining-constraints-and-indexes>`_.
|
||||
|
||||
`using_mapper_options`
|
||||
----------------------
|
||||
The 'using_mapper_options' DSL statement allows you to set up some
|
||||
additional options on your entity mapper. It is meant only to handle the
|
||||
options which are not supported directly by the 'using_options' statement.
|
||||
By opposition to the 'using_options' statement, these options are passed
|
||||
directly to the underlying SQLAlchemy mapper (as keyword arguments)
|
||||
without any processing.
|
||||
|
||||
For further information, please refer to the `SQLAlchemy mapper
|
||||
function's documentation
|
||||
<http://www.sqlalchemy.org/docs/05/reference/orm/mapping.html
|
||||
#sqlalchemy.orm.mapper>`_.
|
||||
|
||||
`using_options_defaults`
|
||||
------------------------
|
||||
The 'using_options_defaults' DSL statement allows you to set up some
|
||||
default options on a custom base class. These will be used as the default value
|
||||
for options of all its subclasses. Note that any option not set within the
|
||||
using_options_defaults (nor specifically on a particular Entity) will use the
|
||||
global defaults, so you don't have to provide a default value for all options,
|
||||
but only those you want to change. Please also note that this statement does
|
||||
not work on normal entities, and the normal using_options statement does not
|
||||
work on base classes (because normal options do not and should not propagate to
|
||||
the children classes).
|
||||
'''
|
||||
|
||||
from sqlalchemy import Integer, String
|
||||
|
||||
from elixir.statements import ClassMutator
|
||||
|
||||
__doc_all__ = ['options_defaults']
|
||||
|
||||
OLD_M2MCOL_NAMEFORMAT = "%(tablename)s_%(key)s%(numifself)s"
|
||||
ALTERNATE_M2MCOL_NAMEFORMAT = "%(inversename)s_%(key)s"
|
||||
|
||||
def default_m2m_column_formatter(data):
|
||||
if data['selfref']:
|
||||
return ALTERNATE_M2MCOL_NAMEFORMAT % data
|
||||
else:
|
||||
return OLD_M2MCOL_NAMEFORMAT % data
|
||||
|
||||
NEW_M2MCOL_NAMEFORMAT = default_m2m_column_formatter
|
||||
|
||||
# format constants
|
||||
FKCOL_NAMEFORMAT = "%(relname)s_%(key)s"
|
||||
M2MCOL_NAMEFORMAT = NEW_M2MCOL_NAMEFORMAT
|
||||
CONSTRAINT_NAMEFORMAT = "%(tablename)s_%(colnames)s_fk"
|
||||
MULTIINHERITANCECOL_NAMEFORMAT = "%(entity)s_%(key)s"
|
||||
|
||||
# other global constants
|
||||
DEFAULT_AUTO_PRIMARYKEY_NAME = "id"
|
||||
DEFAULT_AUTO_PRIMARYKEY_TYPE = Integer
|
||||
DEFAULT_VERSION_ID_COL_NAME = "row_version"
|
||||
DEFAULT_POLYMORPHIC_COL_NAME = "row_type"
|
||||
POLYMORPHIC_COL_SIZE = 40
|
||||
POLYMORPHIC_COL_TYPE = String(POLYMORPHIC_COL_SIZE)
|
||||
|
||||
# debugging/migration help
|
||||
MIGRATION_TO_07_AID = False
|
||||
|
||||
#
|
||||
options_defaults = dict(
|
||||
abstract=False,
|
||||
inheritance='single',
|
||||
polymorphic=True,
|
||||
identity=None,
|
||||
autoload=False,
|
||||
tablename=None,
|
||||
shortnames=False,
|
||||
auto_primarykey=True,
|
||||
version_id_col=False,
|
||||
allowcoloverride=False,
|
||||
order_by=None,
|
||||
resolve_root=None,
|
||||
mapper_options={},
|
||||
table_options={}
|
||||
)
|
||||
|
||||
valid_options = options_defaults.keys() + [
|
||||
'metadata',
|
||||
'session',
|
||||
'collection'
|
||||
]
|
||||
|
||||
|
||||
def using_options_defaults_handler(entity, **kwargs):
|
||||
for kwarg in kwargs:
|
||||
if kwarg not in valid_options:
|
||||
raise Exception("'%s' is not a valid option for Elixir entities."
|
||||
% kwarg)
|
||||
|
||||
# We use __dict__ instead of hasattr to not check its presence within the
|
||||
# parent, and thus update the parent dict instead of creating a local dict.
|
||||
if not entity.__dict__.get('options_defaults'):
|
||||
entity.options_defaults = {}
|
||||
entity.options_defaults.update(kwargs)
|
||||
|
||||
|
||||
def using_options_handler(entity, *args, **kwargs):
|
||||
for kwarg in kwargs:
|
||||
if kwarg in valid_options:
|
||||
setattr(entity._descriptor, kwarg, kwargs[kwarg])
|
||||
else:
|
||||
raise Exception("'%s' is not a valid option for Elixir entities."
|
||||
% kwarg)
|
||||
|
||||
|
||||
def using_table_options_handler(entity, *args, **kwargs):
|
||||
entity._descriptor.table_args.extend(list(args))
|
||||
entity._descriptor.table_options.update(kwargs)
|
||||
|
||||
|
||||
def using_mapper_options_handler(entity, *args, **kwargs):
|
||||
entity._descriptor.mapper_options.update(kwargs)
|
||||
|
||||
|
||||
using_options_defaults = ClassMutator(using_options_defaults_handler)
|
||||
using_options = ClassMutator(using_options_handler)
|
||||
using_table_options = ClassMutator(using_table_options_handler)
|
||||
using_mapper_options = ClassMutator(using_mapper_options_handler)
|
||||
@@ -1,244 +0,0 @@
|
||||
'''
|
||||
This module provides support for defining properties on your entities. It both
|
||||
provides, the `Property` class which acts as a building block for common
|
||||
properties such as fields and relationships (for those, please consult the
|
||||
corresponding modules), but also provides some more specialized properties,
|
||||
such as `ColumnProperty` and `Synonym`. It also provides the GenericProperty
|
||||
class which allows you to wrap any SQLAlchemy property, and its DSL-syntax
|
||||
equivalent: has_property_.
|
||||
|
||||
`has_property`
|
||||
--------------
|
||||
The ``has_property`` statement allows you to define properties which rely on
|
||||
their entity's table (and columns) being defined before they can be declared
|
||||
themselves. The `has_property` statement takes two arguments: first the name of
|
||||
the property to be defined and second a function (often given as an anonymous
|
||||
lambda) taking one argument and returning the desired SQLAlchemy property. That
|
||||
function will be called whenever the entity table is completely defined, and
|
||||
will be given the .c attribute of the entity as argument (as a way to access
|
||||
the entity columns).
|
||||
|
||||
Here is a quick example of how to use ``has_property``.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class OrderLine(Entity):
|
||||
has_field('quantity', Float)
|
||||
has_field('unit_price', Float)
|
||||
has_property('price',
|
||||
lambda c: column_property(
|
||||
(c.quantity * c.unit_price).label('price')))
|
||||
'''
|
||||
|
||||
from elixir.statements import PropertyStatement
|
||||
from sqlalchemy.orm import column_property, synonym
|
||||
|
||||
__doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty',
|
||||
'ColumnProperty']
|
||||
|
||||
class EntityBuilder(object):
|
||||
'''
|
||||
Abstract base class for all entity builders. An Entity builder is a class
|
||||
of objects which can be added to an Entity (usually by using special
|
||||
properties or statements) to "build" that entity. Building an entity,
|
||||
meaning to add columns to its "main" table, create other tables, add
|
||||
properties to its mapper, ... To do so an EntityBuilder must override the
|
||||
corresponding method(s). This is to ensure the different operations happen
|
||||
in the correct order (for example, that the table is fully created before
|
||||
the mapper that use it is defined).
|
||||
'''
|
||||
def create_pk_cols(self):
|
||||
pass
|
||||
|
||||
def create_non_pk_cols(self):
|
||||
pass
|
||||
|
||||
def before_table(self):
|
||||
pass
|
||||
|
||||
def create_tables(self):
|
||||
'''
|
||||
Subclasses may override this method to create tables.
|
||||
'''
|
||||
|
||||
def after_table(self):
|
||||
pass
|
||||
|
||||
def create_properties(self):
|
||||
'''
|
||||
Subclasses may override this method to add properties to the involved
|
||||
entity.
|
||||
'''
|
||||
|
||||
def before_mapper(self):
|
||||
pass
|
||||
|
||||
def after_mapper(self):
|
||||
pass
|
||||
|
||||
def finalize(self):
|
||||
pass
|
||||
|
||||
# helper methods
|
||||
def add_table_column(self, column):
|
||||
self.entity._descriptor.add_column(column)
|
||||
|
||||
def add_mapper_property(self, name, prop):
|
||||
self.entity._descriptor.add_property(name, prop)
|
||||
|
||||
def add_mapper_extension(self, ext):
|
||||
self.entity._descriptor.add_mapper_extension(ext)
|
||||
|
||||
|
||||
class CounterMeta(type):
|
||||
'''
|
||||
A simple meta class which adds a ``_counter`` attribute to the instances of
|
||||
the classes it is used on. This counter is simply incremented for each new
|
||||
instance.
|
||||
'''
|
||||
counter = 0
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
instance = type.__call__(self, *args, **kwargs)
|
||||
instance._counter = CounterMeta.counter
|
||||
CounterMeta.counter += 1
|
||||
return instance
|
||||
|
||||
|
||||
class Property(EntityBuilder):
|
||||
'''
|
||||
Abstract base class for all properties of an Entity.
|
||||
'''
|
||||
__metaclass__ = CounterMeta
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.entity = None
|
||||
self.name = None
|
||||
|
||||
def attach(self, entity, name):
|
||||
"""Attach this property to its entity, using 'name' as name.
|
||||
|
||||
Properties will be attached in the order they were declared.
|
||||
"""
|
||||
self.entity = entity
|
||||
self.name = name
|
||||
|
||||
# register this property as a builder
|
||||
entity._descriptor.builders.append(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "Property(%s, %s)" % (self.name, self.entity)
|
||||
|
||||
|
||||
class GenericProperty(Property):
|
||||
'''
|
||||
Generic catch-all class to wrap an SQLAlchemy property.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class OrderLine(Entity):
|
||||
quantity = Field(Float)
|
||||
unit_price = Field(Numeric)
|
||||
price = GenericProperty(lambda c: column_property(
|
||||
(c.quantity * c.unit_price).label('price')))
|
||||
'''
|
||||
|
||||
def __init__(self, prop, *args, **kwargs):
|
||||
super(GenericProperty, self).__init__(*args, **kwargs)
|
||||
self.prop = prop
|
||||
#XXX: move this to Property?
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def create_properties(self):
|
||||
if hasattr(self.prop, '__call__'):
|
||||
prop_value = self.prop(self.entity.table.c)
|
||||
else:
|
||||
prop_value = self.prop
|
||||
prop_value = self.evaluate_property(prop_value)
|
||||
self.add_mapper_property(self.name, prop_value)
|
||||
|
||||
def evaluate_property(self, prop):
|
||||
if self.args or self.kwargs:
|
||||
raise Exception('superfluous arguments passed to GenericProperty')
|
||||
return prop
|
||||
|
||||
|
||||
class ColumnProperty(GenericProperty):
|
||||
'''
|
||||
A specialized form of the GenericProperty to generate SQLAlchemy
|
||||
``column_property``'s.
|
||||
|
||||
It takes a function (often given as an anonymous lambda) as its first
|
||||
argument. Other arguments and keyword arguments are forwarded to the
|
||||
column_property construct. That first-argument function must accept exactly
|
||||
one argument and must return the desired (scalar-returning) SQLAlchemy
|
||||
ClauseElement.
|
||||
|
||||
The function will be called whenever the entity table is completely
|
||||
defined, and will be given
|
||||
the .c attribute of the table of the entity as argument (as a way to
|
||||
access the entity columns). The ColumnProperty will first wrap your
|
||||
ClauseElement in an
|
||||
"empty" label (ie it will be labelled automatically during queries),
|
||||
then wrap that in a column_property.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class OrderLine(Entity):
|
||||
quantity = Field(Float)
|
||||
unit_price = Field(Numeric)
|
||||
price = ColumnProperty(lambda c: c.quantity * c.unit_price,
|
||||
deferred=True)
|
||||
|
||||
Please look at the `corresponding SQLAlchemy
|
||||
documentation <http://www.sqlalchemy.org/docs/05/mappers.html
|
||||
#sql-expressions-as-mapped-attributes>`_ for details.
|
||||
'''
|
||||
|
||||
def evaluate_property(self, prop):
|
||||
return column_property(prop.label(None), *self.args, **self.kwargs)
|
||||
|
||||
|
||||
class Synonym(GenericProperty):
|
||||
'''
|
||||
This class represents a synonym property of another property (column, ...)
|
||||
of an entity. As opposed to the `synonym` kwarg to the Field class (which
|
||||
share the same goal), this class can be used to define a synonym of a
|
||||
property defined in a parent class (of the current class). On the other
|
||||
hand, it cannot define a synonym for the purpose of using a standard python
|
||||
property in queries. See the Field class for details on that usage.
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
class Person(Entity):
|
||||
name = Field(String(30))
|
||||
primary_email = Field(String(100))
|
||||
email_address = Synonym('primary_email')
|
||||
|
||||
class User(Person):
|
||||
user_name = Synonym('name')
|
||||
password = Field(String(20))
|
||||
'''
|
||||
|
||||
def evaluate_property(self, prop):
|
||||
return synonym(prop, *self.args, **self.kwargs)
|
||||
|
||||
#class Composite(GenericProperty):
|
||||
# def __init__(self, prop):
|
||||
# super(GenericProperty, self).__init__()
|
||||
# self.prop = prop
|
||||
|
||||
# def evaluate_property(self, prop):
|
||||
# return composite(prop.label(self.name))
|
||||
|
||||
#start = Composite(Point, lambda c: (c.x1, c.y1))
|
||||
|
||||
#mapper(Vertex, vertices, properties={
|
||||
# 'start':composite(Point, vertices.c.x1, vertices.c.y1),
|
||||
# 'end':composite(Point, vertices.c.x2, vertices.c.y2)
|
||||
#})
|
||||
|
||||
|
||||
has_property = PropertyStatement(GenericProperty)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,59 +0,0 @@
|
||||
import sys
|
||||
|
||||
MUTATORS = '__elixir_mutators__'
|
||||
|
||||
class ClassMutator(object):
|
||||
'''
|
||||
DSL-style syntax
|
||||
|
||||
A ``ClassMutator`` object represents a DSL term.
|
||||
'''
|
||||
|
||||
def __init__(self, handler):
|
||||
'''
|
||||
Create a new ClassMutator, using the `handler` callable to process it
|
||||
when the time will come.
|
||||
'''
|
||||
self.handler = handler
|
||||
|
||||
# called when a mutator (eg. "has_field(...)") is parsed
|
||||
def __call__(self, *args, **kwargs):
|
||||
# self in this case is the "generic" mutator (eg "has_field")
|
||||
|
||||
# jam this mutator into the class's mutator list
|
||||
class_locals = sys._getframe(1).f_locals
|
||||
mutators = class_locals.setdefault(MUTATORS, [])
|
||||
mutators.append((self, args, kwargs))
|
||||
|
||||
def process(self, entity, *args, **kwargs):
|
||||
'''
|
||||
Process one mutator. This version simply calls the handler callable,
|
||||
but another mutator (sub)class could do more processing.
|
||||
'''
|
||||
self.handler(entity, *args, **kwargs)
|
||||
|
||||
|
||||
#TODO: move this to the super class (to be created here) of EntityMeta
|
||||
def process_mutators(entity):
|
||||
'''
|
||||
Apply all mutators of the given entity. That is, loop over all mutators
|
||||
in the class's mutator list and process them.
|
||||
'''
|
||||
# we don't use getattr here to not inherit from the parent mutators
|
||||
# inadvertantly if the current entity hasn't defined any mutator.
|
||||
mutators = entity.__dict__.get(MUTATORS, [])
|
||||
for mutator, args, kwargs in mutators:
|
||||
mutator.process(entity, *args, **kwargs)
|
||||
|
||||
class Statement(ClassMutator):
|
||||
|
||||
def process(self, entity, *args, **kwargs):
|
||||
builder = self.handler(entity, *args, **kwargs)
|
||||
entity._descriptor.builders.append(builder)
|
||||
|
||||
class PropertyStatement(ClassMutator):
|
||||
|
||||
def process(self, entity, name, *args, **kwargs):
|
||||
prop = self.handler(*args, **kwargs)
|
||||
prop.attach(entity, name)
|
||||
|
||||
+23
-18
@@ -1,15 +1,11 @@
|
||||
# sqlalchemy/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
import sqlalchemy.exc as exceptions
|
||||
|
||||
from sqlalchemy.sql import (
|
||||
from .sql import (
|
||||
alias,
|
||||
and_,
|
||||
asc,
|
||||
@@ -25,6 +21,7 @@ from sqlalchemy.sql import (
|
||||
except_all,
|
||||
exists,
|
||||
extract,
|
||||
false,
|
||||
func,
|
||||
insert,
|
||||
intersect,
|
||||
@@ -42,6 +39,7 @@ from sqlalchemy.sql import (
|
||||
select,
|
||||
subquery,
|
||||
text,
|
||||
true,
|
||||
tuple_,
|
||||
type_coerce,
|
||||
union,
|
||||
@@ -49,7 +47,7 @@ from sqlalchemy.sql import (
|
||||
update,
|
||||
)
|
||||
|
||||
from sqlalchemy.types import (
|
||||
from .types import (
|
||||
BIGINT,
|
||||
BINARY,
|
||||
BLOB,
|
||||
@@ -94,12 +92,11 @@ from sqlalchemy.types import (
|
||||
)
|
||||
|
||||
|
||||
from sqlalchemy.schema import (
|
||||
from .schema import (
|
||||
CheckConstraint,
|
||||
Column,
|
||||
ColumnDefault,
|
||||
Constraint,
|
||||
DDL,
|
||||
DefaultClause,
|
||||
FetchedValue,
|
||||
ForeignKey,
|
||||
@@ -112,17 +109,25 @@ from sqlalchemy.schema import (
|
||||
Table,
|
||||
ThreadLocalMetaData,
|
||||
UniqueConstraint,
|
||||
)
|
||||
|
||||
from sqlalchemy.engine import create_engine, engine_from_config
|
||||
DDL,
|
||||
)
|
||||
|
||||
|
||||
__all__ = sorted(name for name, obj in locals().items()
|
||||
if not (name.startswith('_') or inspect.ismodule(obj)))
|
||||
from .inspection import inspect
|
||||
from .engine import create_engine, engine_from_config
|
||||
|
||||
__version__ = '0.7.10'
|
||||
__version__ = '0.9.1'
|
||||
|
||||
del inspect, sys
|
||||
def __go(lcls):
|
||||
global __all__
|
||||
|
||||
from sqlalchemy import util as _sa_util
|
||||
_sa_util.importlater.resolve_all()
|
||||
from . import events
|
||||
from . import util as _sa_util
|
||||
|
||||
import inspect as _inspect
|
||||
|
||||
__all__ = sorted(name for name, obj in lcls.items()
|
||||
if not (name.startswith('_') or _inspect.ismodule(obj)))
|
||||
|
||||
_sa_util.dependencies.resolve_all("sqlalchemy")
|
||||
__go(locals())
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
processors.c
|
||||
Copyright (C) 2010 Gaetan de Menten gdementen@gmail.com
|
||||
Copyright (C) 2010-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
Copyright (C) 2010-2011 Gaetan de Menten gdementen@gmail.com
|
||||
|
||||
This module is part of SQLAlchemy and is released under
|
||||
the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -9,13 +10,15 @@ the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
#include <Python.h>
|
||||
#include <datetime.h>
|
||||
|
||||
#define MODULE_NAME "cprocessors"
|
||||
#define MODULE_DOC "Module containing C versions of data processing functions."
|
||||
|
||||
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
|
||||
typedef int Py_ssize_t;
|
||||
#define PY_SSIZE_T_MAX INT_MAX
|
||||
#define PY_SSIZE_T_MIN INT_MIN
|
||||
#endif
|
||||
|
||||
|
||||
static PyObject *
|
||||
int_to_boolean(PyObject *self, PyObject *arg)
|
||||
{
|
||||
@@ -25,7 +28,12 @@ int_to_boolean(PyObject *self, PyObject *arg)
|
||||
if (arg == Py_None)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
l = PyLong_AsLong(arg);
|
||||
#else
|
||||
l = PyInt_AsLong(arg);
|
||||
#endif
|
||||
if (l == 0) {
|
||||
res = Py_False;
|
||||
} else if (l == 1) {
|
||||
@@ -64,23 +72,48 @@ to_float(PyObject *self, PyObject *arg)
|
||||
static PyObject *
|
||||
str_to_datetime(PyObject *self, PyObject *arg)
|
||||
{
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *bytes;
|
||||
PyObject *err_bytes;
|
||||
#endif
|
||||
const char *str;
|
||||
int numparsed;
|
||||
unsigned int year, month, day, hour, minute, second, microsecond = 0;
|
||||
PyObject *err_repr;
|
||||
|
||||
if (arg == Py_None)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
bytes = PyUnicode_AsASCIIString(arg);
|
||||
if (bytes == NULL)
|
||||
str = NULL;
|
||||
else
|
||||
str = PyBytes_AS_STRING(bytes);
|
||||
#else
|
||||
str = PyString_AsString(arg);
|
||||
#endif
|
||||
if (str == NULL) {
|
||||
err_repr = PyObject_Repr(arg);
|
||||
if (err_repr == NULL)
|
||||
return NULL;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(err_repr);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse datetime string '%.200s' "
|
||||
"- value is not a string.",
|
||||
PyBytes_AS_STRING(err_bytes));
|
||||
Py_DECREF(err_bytes);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse datetime string '%.200s' "
|
||||
"- value is not a string.",
|
||||
PyString_AsString(err_repr));
|
||||
#endif
|
||||
Py_DECREF(err_repr);
|
||||
return NULL;
|
||||
}
|
||||
@@ -91,15 +124,30 @@ str_to_datetime(PyObject *self, PyObject *arg)
|
||||
not accept "2000-01-01 00:00:00.". I don't know which is better, but they
|
||||
should be coherent.
|
||||
*/
|
||||
if (sscanf(str, "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day,
|
||||
&hour, &minute, &second, µsecond) < 6) {
|
||||
numparsed = sscanf(str, "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day,
|
||||
&hour, &minute, &second, µsecond);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
Py_DECREF(bytes);
|
||||
#endif
|
||||
if (numparsed < 6) {
|
||||
err_repr = PyObject_Repr(arg);
|
||||
if (err_repr == NULL)
|
||||
return NULL;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(err_repr);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse datetime string: %.200s",
|
||||
PyBytes_AS_STRING(err_bytes));
|
||||
Py_DECREF(err_bytes);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse datetime string: %.200s",
|
||||
PyString_AsString(err_repr));
|
||||
#endif
|
||||
Py_DECREF(err_repr);
|
||||
return NULL;
|
||||
}
|
||||
@@ -110,22 +158,47 @@ str_to_datetime(PyObject *self, PyObject *arg)
|
||||
static PyObject *
|
||||
str_to_time(PyObject *self, PyObject *arg)
|
||||
{
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *bytes;
|
||||
PyObject *err_bytes;
|
||||
#endif
|
||||
const char *str;
|
||||
int numparsed;
|
||||
unsigned int hour, minute, second, microsecond = 0;
|
||||
PyObject *err_repr;
|
||||
|
||||
if (arg == Py_None)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
bytes = PyUnicode_AsASCIIString(arg);
|
||||
if (bytes == NULL)
|
||||
str = NULL;
|
||||
else
|
||||
str = PyBytes_AS_STRING(bytes);
|
||||
#else
|
||||
str = PyString_AsString(arg);
|
||||
#endif
|
||||
if (str == NULL) {
|
||||
err_repr = PyObject_Repr(arg);
|
||||
if (err_repr == NULL)
|
||||
return NULL;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(err_repr);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse time string '%.200s' - value is not a string.",
|
||||
PyBytes_AS_STRING(err_bytes));
|
||||
Py_DECREF(err_bytes);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse time string '%.200s' - value is not a string.",
|
||||
PyString_AsString(err_repr));
|
||||
#endif
|
||||
Py_DECREF(err_repr);
|
||||
return NULL;
|
||||
}
|
||||
@@ -136,15 +209,30 @@ str_to_time(PyObject *self, PyObject *arg)
|
||||
not accept "00:00:00.". I don't know which is better, but they should be
|
||||
coherent.
|
||||
*/
|
||||
if (sscanf(str, "%2u:%2u:%2u.%6u", &hour, &minute, &second,
|
||||
µsecond) < 3) {
|
||||
numparsed = sscanf(str, "%2u:%2u:%2u.%6u", &hour, &minute, &second,
|
||||
µsecond);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
Py_DECREF(bytes);
|
||||
#endif
|
||||
if (numparsed < 3) {
|
||||
err_repr = PyObject_Repr(arg);
|
||||
if (err_repr == NULL)
|
||||
return NULL;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(err_repr);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse time string: %.200s",
|
||||
PyBytes_AS_STRING(err_bytes));
|
||||
Py_DECREF(err_bytes);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse time string: %.200s",
|
||||
PyString_AsString(err_repr));
|
||||
#endif
|
||||
Py_DECREF(err_repr);
|
||||
return NULL;
|
||||
}
|
||||
@@ -154,34 +242,73 @@ str_to_time(PyObject *self, PyObject *arg)
|
||||
static PyObject *
|
||||
str_to_date(PyObject *self, PyObject *arg)
|
||||
{
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *bytes;
|
||||
PyObject *err_bytes;
|
||||
#endif
|
||||
const char *str;
|
||||
int numparsed;
|
||||
unsigned int year, month, day;
|
||||
PyObject *err_repr;
|
||||
|
||||
if (arg == Py_None)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
bytes = PyUnicode_AsASCIIString(arg);
|
||||
if (bytes == NULL)
|
||||
str = NULL;
|
||||
else
|
||||
str = PyBytes_AS_STRING(bytes);
|
||||
#else
|
||||
str = PyString_AsString(arg);
|
||||
#endif
|
||||
if (str == NULL) {
|
||||
err_repr = PyObject_Repr(arg);
|
||||
if (err_repr == NULL)
|
||||
return NULL;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(err_repr);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse date string '%.200s' - value is not a string.",
|
||||
PyBytes_AS_STRING(err_bytes));
|
||||
Py_DECREF(err_bytes);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse date string '%.200s' - value is not a string.",
|
||||
PyString_AsString(err_repr));
|
||||
#endif
|
||||
Py_DECREF(err_repr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sscanf(str, "%4u-%2u-%2u", &year, &month, &day) != 3) {
|
||||
numparsed = sscanf(str, "%4u-%2u-%2u", &year, &month, &day);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
Py_DECREF(bytes);
|
||||
#endif
|
||||
if (numparsed != 3) {
|
||||
err_repr = PyObject_Repr(arg);
|
||||
if (err_repr == NULL)
|
||||
return NULL;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(err_repr);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse date string: %.200s",
|
||||
PyBytes_AS_STRING(err_bytes));
|
||||
Py_DECREF(err_bytes);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_ValueError,
|
||||
"Couldn't parse date string: %.200s",
|
||||
PyString_AsString(err_repr));
|
||||
#endif
|
||||
Py_DECREF(err_repr);
|
||||
return NULL;
|
||||
}
|
||||
@@ -218,17 +345,35 @@ UnicodeResultProcessor_init(UnicodeResultProcessor *self, PyObject *args,
|
||||
PyObject *encoding, *errors = NULL;
|
||||
static char *kwlist[] = {"encoding", "errors", NULL};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|U:__init__", kwlist,
|
||||
&encoding, &errors))
|
||||
return -1;
|
||||
#else
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|S:__init__", kwlist,
|
||||
&encoding, &errors))
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
encoding = PyUnicode_AsASCIIString(encoding);
|
||||
#else
|
||||
Py_INCREF(encoding);
|
||||
#endif
|
||||
self->encoding = encoding;
|
||||
|
||||
if (errors) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
errors = PyUnicode_AsASCIIString(errors);
|
||||
#else
|
||||
Py_INCREF(errors);
|
||||
#endif
|
||||
} else {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
errors = PyBytes_FromString("strict");
|
||||
#else
|
||||
errors = PyString_FromString("strict");
|
||||
#endif
|
||||
if (errors == NULL)
|
||||
return -1;
|
||||
}
|
||||
@@ -247,11 +392,19 @@ UnicodeResultProcessor_process(UnicodeResultProcessor *self, PyObject *value)
|
||||
if (value == Py_None)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
if (PyBytes_AsStringAndSize(value, &str, &len))
|
||||
return NULL;
|
||||
|
||||
encoding = PyBytes_AS_STRING(self->encoding);
|
||||
errors = PyBytes_AS_STRING(self->errors);
|
||||
#else
|
||||
if (PyString_AsStringAndSize(value, &str, &len))
|
||||
return NULL;
|
||||
|
||||
encoding = PyString_AS_STRING(self->encoding);
|
||||
errors = PyString_AS_STRING(self->errors);
|
||||
#endif
|
||||
|
||||
return PyUnicode_Decode(str, len, encoding, errors);
|
||||
}
|
||||
@@ -261,7 +414,11 @@ UnicodeResultProcessor_dealloc(UnicodeResultProcessor *self)
|
||||
{
|
||||
Py_XDECREF(self->encoding);
|
||||
Py_XDECREF(self->errors);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
#else
|
||||
self->ob_type->tp_free((PyObject*)self);
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyMethodDef UnicodeResultProcessor_methods[] = {
|
||||
@@ -271,8 +428,7 @@ static PyMethodDef UnicodeResultProcessor_methods[] = {
|
||||
};
|
||||
|
||||
static PyTypeObject UnicodeResultProcessorType = {
|
||||
PyObject_HEAD_INIT(NULL)
|
||||
0, /* ob_size */
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"sqlalchemy.cprocessors.UnicodeResultProcessor", /* tp_name */
|
||||
sizeof(UnicodeResultProcessor), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
@@ -322,7 +478,11 @@ DecimalResultProcessor_init(DecimalResultProcessor *self, PyObject *args,
|
||||
{
|
||||
PyObject *type, *format;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
if (!PyArg_ParseTuple(args, "OU", &type, &format))
|
||||
#else
|
||||
if (!PyArg_ParseTuple(args, "OS", &type, &format))
|
||||
#endif
|
||||
return -1;
|
||||
|
||||
Py_INCREF(type);
|
||||
@@ -342,11 +502,21 @@ DecimalResultProcessor_process(DecimalResultProcessor *self, PyObject *value)
|
||||
if (value == Py_None)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
/* Decimal does not accept float values directly */
|
||||
/* SQLite can also give us an integer here (see [ticket:2432]) */
|
||||
/* XXX: starting with Python 3.1, we could use Decimal.from_float(f),
|
||||
but the result wouldn't be the same */
|
||||
|
||||
args = PyTuple_Pack(1, value);
|
||||
if (args == NULL)
|
||||
return NULL;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
str = PyUnicode_Format(self->format, args);
|
||||
#else
|
||||
str = PyString_Format(self->format, args);
|
||||
#endif
|
||||
|
||||
Py_DECREF(args);
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
@@ -361,7 +531,11 @@ DecimalResultProcessor_dealloc(DecimalResultProcessor *self)
|
||||
{
|
||||
Py_XDECREF(self->type);
|
||||
Py_XDECREF(self->format);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
#else
|
||||
self->ob_type->tp_free((PyObject*)self);
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyMethodDef DecimalResultProcessor_methods[] = {
|
||||
@@ -371,8 +545,7 @@ static PyMethodDef DecimalResultProcessor_methods[] = {
|
||||
};
|
||||
|
||||
static PyTypeObject DecimalResultProcessorType = {
|
||||
PyObject_HEAD_INIT(NULL)
|
||||
0, /* ob_size */
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"sqlalchemy.DecimalResultProcessor", /* tp_name */
|
||||
sizeof(DecimalResultProcessor), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
@@ -412,11 +585,6 @@ static PyTypeObject DecimalResultProcessorType = {
|
||||
0, /* tp_new */
|
||||
};
|
||||
|
||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||
#define PyMODINIT_FUNC void
|
||||
#endif
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"int_to_boolean", int_to_boolean, METH_O,
|
||||
"Convert an integer to a boolean."},
|
||||
@@ -433,23 +601,53 @@ static PyMethodDef module_methods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||
#define PyMODINIT_FUNC void
|
||||
#endif
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
static struct PyModuleDef module_def = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
MODULE_NAME,
|
||||
MODULE_DOC,
|
||||
-1,
|
||||
module_methods
|
||||
};
|
||||
|
||||
#define INITERROR return NULL
|
||||
|
||||
PyObject *
|
||||
PyInit_cprocessors(void)
|
||||
|
||||
#else
|
||||
|
||||
#define INITERROR return
|
||||
|
||||
PyMODINIT_FUNC
|
||||
initcprocessors(void)
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
PyObject *m;
|
||||
|
||||
UnicodeResultProcessorType.tp_new = PyType_GenericNew;
|
||||
if (PyType_Ready(&UnicodeResultProcessorType) < 0)
|
||||
return;
|
||||
INITERROR;
|
||||
|
||||
DecimalResultProcessorType.tp_new = PyType_GenericNew;
|
||||
if (PyType_Ready(&DecimalResultProcessorType) < 0)
|
||||
return;
|
||||
INITERROR;
|
||||
|
||||
m = Py_InitModule3("cprocessors", module_methods,
|
||||
"Module containing C versions of data processing functions.");
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
m = PyModule_Create(&module_def);
|
||||
#else
|
||||
m = Py_InitModule3(MODULE_NAME, module_methods, MODULE_DOC);
|
||||
#endif
|
||||
if (m == NULL)
|
||||
return;
|
||||
INITERROR;
|
||||
|
||||
PyDateTime_IMPORT;
|
||||
|
||||
@@ -460,5 +658,8 @@ initcprocessors(void)
|
||||
Py_INCREF(&DecimalResultProcessorType);
|
||||
PyModule_AddObject(m, "DecimalResultProcessor",
|
||||
(PyObject *)&DecimalResultProcessorType);
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return m;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
resultproxy.c
|
||||
Copyright (C) 2010 Gaetan de Menten gdementen@gmail.com
|
||||
Copyright (C) 2010-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
Copyright (C) 2010-2011 Gaetan de Menten gdementen@gmail.com
|
||||
|
||||
This module is part of SQLAlchemy and is released under
|
||||
the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -8,6 +9,9 @@ the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#define MODULE_NAME "cresultproxy"
|
||||
#define MODULE_DOC "Module containing C versions of core ResultProxy classes."
|
||||
|
||||
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
|
||||
typedef int Py_ssize_t;
|
||||
#define PY_SSIZE_T_MAX INT_MAX
|
||||
@@ -121,7 +125,7 @@ BaseRowProxy_reduce(PyObject *self)
|
||||
if (state == NULL)
|
||||
return NULL;
|
||||
|
||||
module = PyImport_ImportModule("sqlalchemy.engine.base");
|
||||
module = PyImport_ImportModule("sqlalchemy.engine.result");
|
||||
if (module == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -149,7 +153,11 @@ BaseRowProxy_dealloc(BaseRowProxy *self)
|
||||
Py_XDECREF(self->row);
|
||||
Py_XDECREF(self->processors);
|
||||
Py_XDECREF(self->keymap);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||
#else
|
||||
self->ob_type->tp_free((PyObject *)self);
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
@@ -244,14 +252,21 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
PyObject *processor, *value, *processed_value;
|
||||
PyObject *row, *record, *result, *indexobject;
|
||||
PyObject *exc_module, *exception, *cstr_obj;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *bytes;
|
||||
#endif
|
||||
char *cstr_key;
|
||||
long index;
|
||||
int key_fallback = 0;
|
||||
int tuple_check = 0;
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
if (PyInt_CheckExact(key)) {
|
||||
index = PyInt_AS_LONG(key);
|
||||
} else if (PyLong_CheckExact(key)) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (PyLong_CheckExact(key)) {
|
||||
index = PyLong_AsLong(key);
|
||||
if ((index == -1) && PyErr_Occurred())
|
||||
/* -1 can be either the actual value, or an error flag. */
|
||||
@@ -304,7 +319,21 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
cstr_obj = PyObject_Str(key);
|
||||
if (cstr_obj == NULL)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
FIXME: raise encoding error exception (in both versions below)
|
||||
if the key contains non-ascii chars, instead of an
|
||||
InvalidRequestError without any message like in the
|
||||
python version.
|
||||
*/
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
bytes = PyUnicode_AsASCIIString(cstr_obj);
|
||||
if (bytes == NULL)
|
||||
return NULL;
|
||||
cstr_key = PyBytes_AS_STRING(bytes);
|
||||
#else
|
||||
cstr_key = PyString_AsString(cstr_obj);
|
||||
#endif
|
||||
if (cstr_key == NULL) {
|
||||
Py_DECREF(cstr_obj);
|
||||
return NULL;
|
||||
@@ -317,7 +346,11 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
index = PyLong_AsLong(indexobject);
|
||||
#else
|
||||
index = PyInt_AsLong(indexobject);
|
||||
#endif
|
||||
if ((index == -1) && PyErr_Occurred())
|
||||
/* -1 can be either the actual value, or an error flag. */
|
||||
return NULL;
|
||||
@@ -356,13 +389,23 @@ BaseRowProxy_subscript(BaseRowProxy *self, PyObject *key)
|
||||
static PyObject *
|
||||
BaseRowProxy_getitem(PyObject *self, Py_ssize_t i)
|
||||
{
|
||||
return BaseRowProxy_subscript((BaseRowProxy*)self, PyInt_FromSsize_t(i));
|
||||
PyObject *index;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
index = PyLong_FromSsize_t(i);
|
||||
#else
|
||||
index = PyInt_FromSsize_t(i);
|
||||
#endif
|
||||
return BaseRowProxy_subscript((BaseRowProxy*)self, index);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
BaseRowProxy_getattro(BaseRowProxy *self, PyObject *name)
|
||||
{
|
||||
PyObject *tmp;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *err_bytes;
|
||||
#endif
|
||||
|
||||
if (!(tmp = PyObject_GenericGetAttr((PyObject *)self, name))) {
|
||||
if (!PyErr_ExceptionMatches(PyExc_AttributeError))
|
||||
@@ -374,11 +417,23 @@ BaseRowProxy_getattro(BaseRowProxy *self, PyObject *name)
|
||||
|
||||
tmp = BaseRowProxy_subscript(self, name);
|
||||
if (tmp == NULL && PyErr_ExceptionMatches(PyExc_KeyError)) {
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
err_bytes = PyUnicode_AsASCIIString(name);
|
||||
if (err_bytes == NULL)
|
||||
return NULL;
|
||||
PyErr_Format(
|
||||
PyExc_AttributeError,
|
||||
"Could not locate column in row for column '%.200s'",
|
||||
PyBytes_AS_STRING(err_bytes)
|
||||
);
|
||||
#else
|
||||
PyErr_Format(
|
||||
PyExc_AttributeError,
|
||||
"Could not locate column in row for column '%.200s'",
|
||||
PyString_AsString(name)
|
||||
);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
return tmp;
|
||||
@@ -406,7 +461,7 @@ BaseRowProxy_setparent(BaseRowProxy *self, PyObject *value, void *closure)
|
||||
return -1;
|
||||
}
|
||||
|
||||
module = PyImport_ImportModule("sqlalchemy.engine.base");
|
||||
module = PyImport_ImportModule("sqlalchemy.engine.result");
|
||||
if (module == NULL)
|
||||
return -1;
|
||||
|
||||
@@ -564,8 +619,7 @@ static PyMappingMethods BaseRowProxy_as_mapping = {
|
||||
};
|
||||
|
||||
static PyTypeObject BaseRowProxyType = {
|
||||
PyObject_HEAD_INIT(NULL)
|
||||
0, /* ob_size */
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
"sqlalchemy.cresultproxy.BaseRowProxy", /* tp_name */
|
||||
sizeof(BaseRowProxy), /* tp_basicsize */
|
||||
0, /* tp_itemsize */
|
||||
@@ -605,34 +659,60 @@ static PyTypeObject BaseRowProxyType = {
|
||||
0 /* tp_new */
|
||||
};
|
||||
|
||||
|
||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||
#define PyMODINIT_FUNC void
|
||||
#endif
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"safe_rowproxy_reconstructor", safe_rowproxy_reconstructor, METH_VARARGS,
|
||||
"reconstruct a RowProxy instance from its pickled form."},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||
#define PyMODINIT_FUNC void
|
||||
#endif
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
static struct PyModuleDef module_def = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
MODULE_NAME,
|
||||
MODULE_DOC,
|
||||
-1,
|
||||
module_methods
|
||||
};
|
||||
|
||||
#define INITERROR return NULL
|
||||
|
||||
PyObject *
|
||||
PyInit_cresultproxy(void)
|
||||
|
||||
#else
|
||||
|
||||
#define INITERROR return
|
||||
|
||||
PyMODINIT_FUNC
|
||||
initcresultproxy(void)
|
||||
|
||||
#endif
|
||||
|
||||
{
|
||||
PyObject *m;
|
||||
|
||||
BaseRowProxyType.tp_new = PyType_GenericNew;
|
||||
if (PyType_Ready(&BaseRowProxyType) < 0)
|
||||
return;
|
||||
INITERROR;
|
||||
|
||||
m = Py_InitModule3("cresultproxy", module_methods,
|
||||
"Module containing C versions of core ResultProxy classes.");
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
m = PyModule_Create(&module_def);
|
||||
#else
|
||||
m = Py_InitModule3(MODULE_NAME, module_methods, MODULE_DOC);
|
||||
#endif
|
||||
if (m == NULL)
|
||||
return;
|
||||
INITERROR;
|
||||
|
||||
Py_INCREF(&BaseRowProxyType);
|
||||
PyModule_AddObject(m, "BaseRowProxy", (PyObject *)&BaseRowProxyType);
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return m;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
utils.c
|
||||
Copyright (C) 2012-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
|
||||
This module is part of SQLAlchemy and is released under
|
||||
the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#define MODULE_NAME "cutils"
|
||||
#define MODULE_DOC "Module containing C versions of utility functions."
|
||||
|
||||
/*
|
||||
Given arguments from the calling form *multiparams, **params,
|
||||
return a list of bind parameter structures, usually a list of
|
||||
dictionaries.
|
||||
|
||||
In the case of 'raw' execution which accepts positional parameters,
|
||||
it may be a list of tuples or lists.
|
||||
|
||||
*/
|
||||
static PyObject *
|
||||
distill_params(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *multiparams, *params;
|
||||
PyObject *enclosing_list, *double_enclosing_list;
|
||||
PyObject *zero_element, *zero_element_item;
|
||||
Py_ssize_t multiparam_size, zero_element_length;
|
||||
|
||||
if (!PyArg_UnpackTuple(args, "_distill_params", 2, 2, &multiparams, ¶ms)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (multiparams != Py_None) {
|
||||
multiparam_size = PyTuple_Size(multiparams);
|
||||
if (multiparam_size < 0) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
multiparam_size = 0;
|
||||
}
|
||||
|
||||
if (multiparam_size == 0) {
|
||||
if (params != Py_None && PyDict_Size(params) != 0) {
|
||||
enclosing_list = PyList_New(1);
|
||||
if (enclosing_list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(params);
|
||||
if (PyList_SetItem(enclosing_list, 0, params) == -1) {
|
||||
Py_DECREF(params);
|
||||
Py_DECREF(enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
enclosing_list = PyList_New(0);
|
||||
if (enclosing_list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return enclosing_list;
|
||||
}
|
||||
else if (multiparam_size == 1) {
|
||||
zero_element = PyTuple_GetItem(multiparams, 0);
|
||||
if (PyTuple_Check(zero_element) || PyList_Check(zero_element)) {
|
||||
zero_element_length = PySequence_Length(zero_element);
|
||||
|
||||
if (zero_element_length != 0) {
|
||||
zero_element_item = PySequence_GetItem(zero_element, 0);
|
||||
if (zero_element_item == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
zero_element_item = NULL;
|
||||
}
|
||||
|
||||
if (zero_element_length == 0 ||
|
||||
(
|
||||
PyObject_HasAttrString(zero_element_item, "__iter__") &&
|
||||
!PyObject_HasAttrString(zero_element_item, "strip")
|
||||
)
|
||||
) {
|
||||
/*
|
||||
* execute(stmt, [{}, {}, {}, ...])
|
||||
* execute(stmt, [(), (), (), ...])
|
||||
*/
|
||||
Py_XDECREF(zero_element_item);
|
||||
Py_INCREF(zero_element);
|
||||
return zero_element;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* execute(stmt, ("value", "value"))
|
||||
*/
|
||||
Py_XDECREF(zero_element_item);
|
||||
enclosing_list = PyList_New(1);
|
||||
if (enclosing_list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(zero_element);
|
||||
if (PyList_SetItem(enclosing_list, 0, zero_element) == -1) {
|
||||
Py_DECREF(zero_element);
|
||||
Py_DECREF(enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
return enclosing_list;
|
||||
}
|
||||
}
|
||||
else if (PyObject_HasAttrString(zero_element, "keys")) {
|
||||
/*
|
||||
* execute(stmt, {"key":"value"})
|
||||
*/
|
||||
enclosing_list = PyList_New(1);
|
||||
if (enclosing_list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(zero_element);
|
||||
if (PyList_SetItem(enclosing_list, 0, zero_element) == -1) {
|
||||
Py_DECREF(zero_element);
|
||||
Py_DECREF(enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
return enclosing_list;
|
||||
} else {
|
||||
enclosing_list = PyList_New(1);
|
||||
if (enclosing_list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
double_enclosing_list = PyList_New(1);
|
||||
if (double_enclosing_list == NULL) {
|
||||
Py_DECREF(enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(zero_element);
|
||||
if (PyList_SetItem(enclosing_list, 0, zero_element) == -1) {
|
||||
Py_DECREF(zero_element);
|
||||
Py_DECREF(enclosing_list);
|
||||
Py_DECREF(double_enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
if (PyList_SetItem(double_enclosing_list, 0, enclosing_list) == -1) {
|
||||
Py_DECREF(zero_element);
|
||||
Py_DECREF(enclosing_list);
|
||||
Py_DECREF(double_enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
return double_enclosing_list;
|
||||
}
|
||||
}
|
||||
else {
|
||||
zero_element = PyTuple_GetItem(multiparams, 0);
|
||||
if (PyObject_HasAttrString(zero_element, "__iter__") &&
|
||||
!PyObject_HasAttrString(zero_element, "strip")
|
||||
) {
|
||||
Py_INCREF(multiparams);
|
||||
return multiparams;
|
||||
}
|
||||
else {
|
||||
enclosing_list = PyList_New(1);
|
||||
if (enclosing_list == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(multiparams);
|
||||
if (PyList_SetItem(enclosing_list, 0, multiparams) == -1) {
|
||||
Py_DECREF(multiparams);
|
||||
Py_DECREF(enclosing_list);
|
||||
return NULL;
|
||||
}
|
||||
return enclosing_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"_distill_params", distill_params, METH_VARARGS,
|
||||
"Distill an execute() parameter structure."},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||
#define PyMODINIT_FUNC void
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
static struct PyModuleDef module_def = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
MODULE_NAME,
|
||||
MODULE_DOC,
|
||||
-1,
|
||||
module_methods
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *
|
||||
PyInit_cutils(void)
|
||||
#else
|
||||
PyMODINIT_FUNC
|
||||
initcutils(void)
|
||||
#endif
|
||||
{
|
||||
PyObject *m;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
m = PyModule_Create(&module_def);
|
||||
#else
|
||||
m = Py_InitModule3(MODULE_NAME, module_methods, MODULE_DOC);
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
if (m == NULL)
|
||||
return NULL;
|
||||
return m;
|
||||
#else
|
||||
if (m == NULL)
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# connectors/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -7,4 +7,3 @@
|
||||
|
||||
class Connector(object):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# connectors/mxodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -22,14 +22,15 @@ import sys
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from sqlalchemy.connectors import Connector
|
||||
from . import Connector
|
||||
|
||||
|
||||
class MxODBCConnector(Connector):
|
||||
driver='mxodbc'
|
||||
driver = 'mxodbc'
|
||||
|
||||
supports_sane_multi_rowcount = False
|
||||
supports_unicode_statements = False
|
||||
supports_unicode_binds = False
|
||||
supports_unicode_statements = True
|
||||
supports_unicode_binds = True
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
@@ -47,7 +48,7 @@ class MxODBCConnector(Connector):
|
||||
elif platform == 'darwin':
|
||||
from mx.ODBC import iODBC as module
|
||||
else:
|
||||
raise ImportError, "Unrecognized platform for mxODBC import"
|
||||
raise ImportError("Unrecognized platform for mxODBC import")
|
||||
return module
|
||||
|
||||
@classmethod
|
||||
@@ -73,15 +74,15 @@ class MxODBCConnector(Connector):
|
||||
emit Python standard warnings.
|
||||
"""
|
||||
from mx.ODBC.Error import Warning as MxOdbcWarning
|
||||
def error_handler(connection, cursor, errorclass, errorvalue):
|
||||
|
||||
def error_handler(connection, cursor, errorclass, errorvalue):
|
||||
if issubclass(errorclass, MxOdbcWarning):
|
||||
errorclass.__bases__ = (Warning,)
|
||||
warnings.warn(message=str(errorvalue),
|
||||
category=errorclass,
|
||||
stacklevel=2)
|
||||
else:
|
||||
raise errorclass, errorvalue
|
||||
raise errorclass(errorvalue)
|
||||
return error_handler
|
||||
|
||||
def create_connect_args(self, url):
|
||||
@@ -130,21 +131,19 @@ class MxODBCConnector(Connector):
|
||||
version.append(n)
|
||||
return tuple(version)
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
def _get_direct(self, context):
|
||||
if context:
|
||||
native_odbc_execute = context.execution_options.\
|
||||
get('native_odbc_execute', 'auto')
|
||||
if native_odbc_execute is True:
|
||||
# user specified native_odbc_execute=True
|
||||
cursor.execute(statement, parameters)
|
||||
elif native_odbc_execute is False:
|
||||
# user specified native_odbc_execute=False
|
||||
cursor.executedirect(statement, parameters)
|
||||
elif context.is_crud:
|
||||
# statement is UPDATE, DELETE, INSERT
|
||||
cursor.execute(statement, parameters)
|
||||
else:
|
||||
# all other statements
|
||||
cursor.executedirect(statement, parameters)
|
||||
# default to direct=True in all cases, is more generally
|
||||
# compatible especially with SQL Server
|
||||
return False if native_odbc_execute is True else True
|
||||
else:
|
||||
cursor.executedirect(statement, parameters)
|
||||
return True
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
cursor.executemany(
|
||||
statement, parameters, direct=self._get_direct(context))
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
cursor.execute(statement, parameters, direct=self._get_direct(context))
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
# connectors/mysqldb.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Define behaviors common to MySQLdb dialects.
|
||||
|
||||
Currently includes MySQL and Drizzle.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.connectors import Connector
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
|
||||
from sqlalchemy import processors
|
||||
from . import Connector
|
||||
from ..engine import base as engine_base, default
|
||||
from ..sql import operators as sql_operators
|
||||
from .. import exc, log, schema, sql, types as sqltypes, util, processors
|
||||
import re
|
||||
|
||||
|
||||
# the subclassing of Connector by all classes
|
||||
# here is not strictly necessary
|
||||
|
||||
|
||||
class MySQLDBExecutionContext(Connector):
|
||||
|
||||
@property
|
||||
@@ -23,19 +30,23 @@ class MySQLDBExecutionContext(Connector):
|
||||
else:
|
||||
return self.cursor.rowcount
|
||||
|
||||
|
||||
class MySQLDBCompiler(Connector):
|
||||
def visit_mod(self, binary, **kw):
|
||||
return self.process(binary.left) + " %% " + self.process(binary.right)
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
|
||||
|
||||
class MySQLDBIdentifierPreparer(Connector):
|
||||
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace("%", "%%")
|
||||
|
||||
|
||||
class MySQLDBConnector(Connector):
|
||||
driver = 'mysqldb'
|
||||
supports_unicode_statements = False
|
||||
@@ -76,7 +87,8 @@ class MySQLDBConnector(Connector):
|
||||
# query string.
|
||||
|
||||
ssl = {}
|
||||
for key in ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']:
|
||||
keys = ['ssl_ca', 'ssl_key', 'ssl_cert', 'ssl_capath', 'ssl_cipher']
|
||||
for key in keys:
|
||||
if key in opts:
|
||||
ssl[key[4:]] = opts[key]
|
||||
util.coerce_kw_type(ssl, key[4:], str)
|
||||
@@ -148,4 +160,3 @@ class MySQLDBConnector(Connector):
|
||||
"combination of MySQL server and MySQL-python. "
|
||||
"MySQL-python >= 1.2.2 is recommended. Assuming latin1.")
|
||||
return 'latin1'
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
# connectors/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.connectors import Connector
|
||||
from sqlalchemy.util import asbool
|
||||
from . import Connector
|
||||
from .. import util
|
||||
|
||||
|
||||
import sys
|
||||
import re
|
||||
import urllib
|
||||
|
||||
|
||||
class PyODBCConnector(Connector):
|
||||
driver='pyodbc'
|
||||
driver = 'pyodbc'
|
||||
|
||||
supports_sane_multi_rowcount = False
|
||||
# PyODBC unicode is broken on UCS-4 builds
|
||||
supports_unicode = sys.maxunicode == 65535
|
||||
supports_unicode_statements = supports_unicode
|
||||
|
||||
if util.py2k:
|
||||
# PyODBC unicode is broken on UCS-4 builds
|
||||
supports_unicode = sys.maxunicode == 65535
|
||||
supports_unicode_statements = supports_unicode
|
||||
|
||||
supports_native_decimal = True
|
||||
default_paramstyle = 'named'
|
||||
|
||||
@@ -55,15 +59,15 @@ class PyODBCConnector(Connector):
|
||||
connect_args = {}
|
||||
for param in ('ansi', 'unicode_results', 'autocommit'):
|
||||
if param in keys:
|
||||
connect_args[param] = asbool(keys.pop(param))
|
||||
connect_args[param] = util.asbool(keys.pop(param))
|
||||
|
||||
if 'odbc_connect' in keys:
|
||||
connectors = [urllib.unquote_plus(keys.pop('odbc_connect'))]
|
||||
connectors = [util.unquote_plus(keys.pop('odbc_connect'))]
|
||||
else:
|
||||
dsn_connection = 'dsn' in keys or \
|
||||
('host' in keys and 'database' not in keys)
|
||||
if dsn_connection:
|
||||
connectors= ['dsn=%s' % (keys.pop('host', '') or \
|
||||
connectors = ['dsn=%s' % (keys.pop('host', '') or \
|
||||
keys.pop('dsn', ''))]
|
||||
else:
|
||||
port = ''
|
||||
@@ -73,7 +77,7 @@ class PyODBCConnector(Connector):
|
||||
connectors = ["DRIVER={%s}" %
|
||||
keys.pop('driver', self.pyodbc_driver_name),
|
||||
'Server=%s%s' % (keys.pop('host', ''), port),
|
||||
'Database=%s' % keys.pop('database', '') ]
|
||||
'Database=%s' % keys.pop('database', '')]
|
||||
|
||||
user = keys.pop("user", None)
|
||||
if user:
|
||||
@@ -90,8 +94,8 @@ class PyODBCConnector(Connector):
|
||||
connectors.append("AutoTranslate=%s" %
|
||||
keys.pop("odbc_autotranslate"))
|
||||
|
||||
connectors.extend(['%s=%s' % (k,v) for k,v in keys.iteritems()])
|
||||
return [[";".join (connectors)], connect_args]
|
||||
connectors.extend(['%s=%s' % (k, v) for k, v in keys.items()])
|
||||
return [[";".join(connectors)], connect_args]
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.ProgrammingError):
|
||||
@@ -117,19 +121,22 @@ class PyODBCConnector(Connector):
|
||||
))
|
||||
|
||||
if self.freetds:
|
||||
self.freetds_driver_version = dbapi_con.getinfo(pyodbc.SQL_DRIVER_VER)
|
||||
self.freetds_driver_version = dbapi_con.getinfo(
|
||||
pyodbc.SQL_DRIVER_VER)
|
||||
|
||||
self.supports_unicode_statements = (
|
||||
not util.py2k or
|
||||
(not self.freetds and not self.easysoft)
|
||||
)
|
||||
|
||||
# the "Py2K only" part here is theoretical.
|
||||
# have not tried pyodbc + python3.1 yet.
|
||||
# Py2K
|
||||
self.supports_unicode_statements = not self.freetds and not self.easysoft
|
||||
if self._user_supports_unicode_binds is not None:
|
||||
self.supports_unicode_binds = self._user_supports_unicode_binds
|
||||
elif util.py2k:
|
||||
self.supports_unicode_binds = (
|
||||
not self.freetds or self.freetds_driver_version >= '0.91'
|
||||
) and not self.easysoft
|
||||
else:
|
||||
self.supports_unicode_binds = (not self.freetds or
|
||||
self.freetds_driver_version >= '0.91'
|
||||
) and not self.easysoft
|
||||
# end Py2K
|
||||
self.supports_unicode_binds = True
|
||||
|
||||
# run other initialization which asks for user name, etc.
|
||||
super(PyODBCConnector, self).initialize(connection)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# connectors/zxJDBC.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import sys
|
||||
from sqlalchemy.connectors import Connector
|
||||
from . import Connector
|
||||
|
||||
|
||||
class ZxJDBCConnector(Connector):
|
||||
driver = 'zxjdbc'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# databases/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -8,26 +8,20 @@
|
||||
compatibility with pre 0.6 versions.
|
||||
|
||||
"""
|
||||
from sqlalchemy.dialects.sqlite import base as sqlite
|
||||
from sqlalchemy.dialects.postgresql import base as postgresql
|
||||
from ..dialects.sqlite import base as sqlite
|
||||
from ..dialects.postgresql import base as postgresql
|
||||
postgres = postgresql
|
||||
from sqlalchemy.dialects.mysql import base as mysql
|
||||
from sqlalchemy.dialects.drizzle import base as drizzle
|
||||
from sqlalchemy.dialects.oracle import base as oracle
|
||||
from sqlalchemy.dialects.firebird import base as firebird
|
||||
from sqlalchemy.dialects.maxdb import base as maxdb
|
||||
from sqlalchemy.dialects.informix import base as informix
|
||||
from sqlalchemy.dialects.mssql import base as mssql
|
||||
from sqlalchemy.dialects.access import base as access
|
||||
from sqlalchemy.dialects.sybase import base as sybase
|
||||
from ..dialects.mysql import base as mysql
|
||||
from ..dialects.drizzle import base as drizzle
|
||||
from ..dialects.oracle import base as oracle
|
||||
from ..dialects.firebird import base as firebird
|
||||
from ..dialects.mssql import base as mssql
|
||||
from ..dialects.sybase import base as sybase
|
||||
|
||||
|
||||
__all__ = (
|
||||
'access',
|
||||
'drizzle',
|
||||
'firebird',
|
||||
'informix',
|
||||
'maxdb',
|
||||
'mssql',
|
||||
'mysql',
|
||||
'postgresql',
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
# dialects/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
__all__ = (
|
||||
# 'access',
|
||||
'drizzle',
|
||||
'firebird',
|
||||
# 'informix',
|
||||
# 'maxdb',
|
||||
'mssql',
|
||||
'mysql',
|
||||
'oracle',
|
||||
@@ -17,3 +14,31 @@ __all__ = (
|
||||
'sqlite',
|
||||
'sybase',
|
||||
)
|
||||
|
||||
from .. import util
|
||||
|
||||
def _auto_fn(name):
|
||||
"""default dialect importer.
|
||||
|
||||
plugs into the :class:`.PluginLoader`
|
||||
as a first-hit system.
|
||||
|
||||
"""
|
||||
if "." in name:
|
||||
dialect, driver = name.split(".")
|
||||
else:
|
||||
dialect = name
|
||||
driver = "base"
|
||||
try:
|
||||
module = __import__('sqlalchemy.dialects.%s' % (dialect, )).dialects
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
module = getattr(module, dialect)
|
||||
if hasattr(module, driver):
|
||||
module = getattr(module, driver)
|
||||
return lambda: module.dialect
|
||||
else:
|
||||
return None
|
||||
|
||||
registry = util.PluginLoader("sqlalchemy.dialects", auto_fn=_auto_fn)
|
||||
|
||||
@@ -1,451 +0,0 @@
|
||||
# access/base.py
|
||||
# Copyright (C) 2007-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2007 Paul Johnston, paj@pajhome.org.uk
|
||||
# Portions derived from jet2sql.py by Matt Keranen, mksql@yahoo.com
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for the Microsoft Access database.
|
||||
|
||||
.. note::
|
||||
|
||||
The Access dialect is **non-functional as of SQLAlchemy 0.6**,
|
||||
pending development efforts to bring it up-to-date.
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy import sql, schema, types, exc, pool
|
||||
from sqlalchemy.sql import compiler, expression
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
from sqlalchemy import processors
|
||||
|
||||
class AcNumeric(types.Numeric):
|
||||
def get_col_spec(self):
|
||||
return "NUMERIC"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
return processors.to_str
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
return None
|
||||
|
||||
class AcFloat(types.Float):
|
||||
def get_col_spec(self):
|
||||
return "FLOAT"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
"""By converting to string, we can use Decimal types round-trip."""
|
||||
return processors.to_str
|
||||
|
||||
class AcInteger(types.Integer):
|
||||
def get_col_spec(self):
|
||||
return "INTEGER"
|
||||
|
||||
class AcTinyInteger(types.Integer):
|
||||
def get_col_spec(self):
|
||||
return "TINYINT"
|
||||
|
||||
class AcSmallInteger(types.SmallInteger):
|
||||
def get_col_spec(self):
|
||||
return "SMALLINT"
|
||||
|
||||
class AcDateTime(types.DateTime):
|
||||
def get_col_spec(self):
|
||||
return "DATETIME"
|
||||
|
||||
class AcDate(types.Date):
|
||||
|
||||
def get_col_spec(self):
|
||||
return "DATETIME"
|
||||
|
||||
class AcText(types.Text):
|
||||
def get_col_spec(self):
|
||||
return "MEMO"
|
||||
|
||||
class AcString(types.String):
|
||||
def get_col_spec(self):
|
||||
return "TEXT" + (self.length and ("(%d)" % self.length) or "")
|
||||
|
||||
class AcUnicode(types.Unicode):
|
||||
def get_col_spec(self):
|
||||
return "TEXT" + (self.length and ("(%d)" % self.length) or "")
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
return None
|
||||
|
||||
class AcChar(types.CHAR):
|
||||
def get_col_spec(self):
|
||||
return "TEXT" + (self.length and ("(%d)" % self.length) or "")
|
||||
|
||||
class AcBinary(types.LargeBinary):
|
||||
def get_col_spec(self):
|
||||
return "BINARY"
|
||||
|
||||
class AcBoolean(types.Boolean):
|
||||
def get_col_spec(self):
|
||||
return "YESNO"
|
||||
|
||||
class AcTimeStamp(types.TIMESTAMP):
|
||||
def get_col_spec(self):
|
||||
return "TIMESTAMP"
|
||||
|
||||
class AccessExecutionContext(default.DefaultExecutionContext):
|
||||
def _has_implicit_sequence(self, column):
|
||||
if column.primary_key and column.autoincrement:
|
||||
if isinstance(column.type, types.Integer) and \
|
||||
not column.foreign_keys:
|
||||
if column.default is None or \
|
||||
(isinstance(column.default, schema.Sequence) and \
|
||||
column.default.optional):
|
||||
return True
|
||||
return False
|
||||
|
||||
def post_exec(self):
|
||||
"""If we inserted into a row with a COUNTER column, fetch the ID"""
|
||||
|
||||
if self.compiled.isinsert:
|
||||
tbl = self.compiled.statement.table
|
||||
if not hasattr(tbl, 'has_sequence'):
|
||||
tbl.has_sequence = None
|
||||
for column in tbl.c:
|
||||
if getattr(column, 'sequence', False) or \
|
||||
self._has_implicit_sequence(column):
|
||||
tbl.has_sequence = column
|
||||
break
|
||||
|
||||
if bool(tbl.has_sequence):
|
||||
# TBD: for some reason _last_inserted_ids doesn't exist here
|
||||
# (but it does at corresponding point in mssql???)
|
||||
#if not len(self._last_inserted_ids) or
|
||||
# self._last_inserted_ids[0] is None:
|
||||
self.cursor.execute("SELECT @@identity AS lastrowid")
|
||||
row = self.cursor.fetchone()
|
||||
self._last_inserted_ids = [int(row[0])]
|
||||
#+ self._last_inserted_ids[1:]
|
||||
# print "LAST ROW ID", self._last_inserted_ids
|
||||
|
||||
super(AccessExecutionContext, self).post_exec()
|
||||
|
||||
|
||||
const, daoEngine = None, None
|
||||
class AccessDialect(default.DefaultDialect):
|
||||
colspecs = {
|
||||
types.Unicode : AcUnicode,
|
||||
types.Integer : AcInteger,
|
||||
types.SmallInteger: AcSmallInteger,
|
||||
types.Numeric : AcNumeric,
|
||||
types.Float : AcFloat,
|
||||
types.DateTime : AcDateTime,
|
||||
types.Date : AcDate,
|
||||
types.String : AcString,
|
||||
types.LargeBinary : AcBinary,
|
||||
types.Boolean : AcBoolean,
|
||||
types.Text : AcText,
|
||||
types.CHAR: AcChar,
|
||||
types.TIMESTAMP: AcTimeStamp,
|
||||
}
|
||||
name = 'access'
|
||||
supports_sane_rowcount = False
|
||||
supports_sane_multi_rowcount = False
|
||||
|
||||
ported_sqla_06 = False
|
||||
|
||||
def type_descriptor(self, typeobj):
|
||||
newobj = types.adapt_type(typeobj, self.colspecs)
|
||||
return newobj
|
||||
|
||||
def __init__(self, **params):
|
||||
super(AccessDialect, self).__init__(**params)
|
||||
self.text_as_varchar = False
|
||||
self._dtbs = None
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
import win32com.client, pythoncom
|
||||
|
||||
global const, daoEngine
|
||||
if const is None:
|
||||
const = win32com.client.constants
|
||||
for suffix in (".36", ".35", ".30"):
|
||||
try:
|
||||
daoEngine = win32com.client.\
|
||||
gencache.\
|
||||
EnsureDispatch("DAO.DBEngine" + suffix)
|
||||
break
|
||||
except pythoncom.com_error:
|
||||
pass
|
||||
else:
|
||||
raise exc.InvalidRequestError(
|
||||
"Can't find a DB engine. Check "
|
||||
"http://support.microsoft.com/kb/239114 for details.")
|
||||
|
||||
import pyodbc as module
|
||||
return module
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
connectors = ["Driver={Microsoft Access Driver (*.mdb)}"]
|
||||
connectors.append("Dbq=%s" % opts["database"])
|
||||
user = opts.get("username", None)
|
||||
if user:
|
||||
connectors.append("UID=%s" % user)
|
||||
connectors.append("PWD=%s" % opts.get("password", ""))
|
||||
return [[";".join(connectors)], {}]
|
||||
|
||||
def last_inserted_ids(self):
|
||||
return self.context.last_inserted_ids
|
||||
|
||||
def do_execute(self, cursor, statement, params, context=None):
|
||||
if params == {}:
|
||||
params = ()
|
||||
super(AccessDialect, self).\
|
||||
do_execute(cursor, statement, params, **kwargs)
|
||||
|
||||
def _execute(self, c, statement, parameters):
|
||||
try:
|
||||
if parameters == {}:
|
||||
parameters = ()
|
||||
c.execute(statement, parameters)
|
||||
self.context.rowcount = c.rowcount
|
||||
except Exception, e:
|
||||
raise exc.DBAPIError.instance(statement, parameters, e)
|
||||
|
||||
def has_table(self, connection, tablename, schema=None):
|
||||
# This approach seems to be more reliable that using DAO
|
||||
try:
|
||||
connection.execute('select top 1 * from [%s]' % tablename)
|
||||
return True
|
||||
except Exception, e:
|
||||
return False
|
||||
|
||||
def reflecttable(self, connection, table, include_columns):
|
||||
# This is defined in the function, as it relies on win32com constants,
|
||||
# that aren't imported until dbapi method is called
|
||||
if not hasattr(self, 'ischema_names'):
|
||||
self.ischema_names = {
|
||||
const.dbByte: AcBinary,
|
||||
const.dbInteger: AcInteger,
|
||||
const.dbLong: AcInteger,
|
||||
const.dbSingle: AcFloat,
|
||||
const.dbDouble: AcFloat,
|
||||
const.dbDate: AcDateTime,
|
||||
const.dbLongBinary: AcBinary,
|
||||
const.dbMemo: AcText,
|
||||
const.dbBoolean: AcBoolean,
|
||||
const.dbText: AcUnicode, # All Access strings are
|
||||
# unicode
|
||||
const.dbCurrency: AcNumeric,
|
||||
}
|
||||
|
||||
# A fresh DAO connection is opened for each reflection
|
||||
# This is necessary, so we get the latest updates
|
||||
dtbs = daoEngine.OpenDatabase(connection.engine.url.database)
|
||||
|
||||
try:
|
||||
for tbl in dtbs.TableDefs:
|
||||
if tbl.Name.lower() == table.name.lower():
|
||||
break
|
||||
else:
|
||||
raise exc.NoSuchTableError(table.name)
|
||||
|
||||
for col in tbl.Fields:
|
||||
coltype = self.ischema_names[col.Type]
|
||||
if col.Type == const.dbText:
|
||||
coltype = coltype(col.Size)
|
||||
|
||||
colargs = \
|
||||
{
|
||||
'nullable': not(col.Required or
|
||||
col.Attributes & const.dbAutoIncrField),
|
||||
}
|
||||
default = col.DefaultValue
|
||||
|
||||
if col.Attributes & const.dbAutoIncrField:
|
||||
colargs['default'] = schema.Sequence(col.Name + '_seq')
|
||||
elif default:
|
||||
if col.Type == const.dbBoolean:
|
||||
default = default == 'Yes' and '1' or '0'
|
||||
colargs['server_default'] = \
|
||||
schema.DefaultClause(sql.text(default))
|
||||
|
||||
table.append_column(
|
||||
schema.Column(col.Name, coltype, **colargs))
|
||||
|
||||
# TBD: check constraints
|
||||
|
||||
# Find primary key columns first
|
||||
for idx in tbl.Indexes:
|
||||
if idx.Primary:
|
||||
for col in idx.Fields:
|
||||
thecol = table.c[col.Name]
|
||||
table.primary_key.add(thecol)
|
||||
if isinstance(thecol.type, AcInteger) and \
|
||||
not (thecol.default and
|
||||
isinstance(
|
||||
thecol.default.arg,
|
||||
schema.Sequence
|
||||
)):
|
||||
thecol.autoincrement = False
|
||||
|
||||
# Then add other indexes
|
||||
for idx in tbl.Indexes:
|
||||
if not idx.Primary:
|
||||
if len(idx.Fields) == 1:
|
||||
col = table.c[idx.Fields[0].Name]
|
||||
if not col.primary_key:
|
||||
col.index = True
|
||||
col.unique = idx.Unique
|
||||
else:
|
||||
pass # TBD: multi-column indexes
|
||||
|
||||
|
||||
for fk in dtbs.Relations:
|
||||
if fk.ForeignTable != table.name:
|
||||
continue
|
||||
scols = [c.ForeignName for c in fk.Fields]
|
||||
rcols = ['%s.%s' % (fk.Table, c.Name) for c in fk.Fields]
|
||||
table.append_constraint(
|
||||
schema.ForeignKeyConstraint(scols, rcols,\
|
||||
link_to_name=True))
|
||||
|
||||
finally:
|
||||
dtbs.Close()
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
# A fresh DAO connection is opened for each reflection
|
||||
# This is necessary, so we get the latest updates
|
||||
dtbs = daoEngine.OpenDatabase(connection.engine.url.database)
|
||||
|
||||
names = [t.Name for t in dtbs.TableDefs
|
||||
if t.Name[:4] != "MSys" and t.Name[:4] != "~TMP"]
|
||||
dtbs.Close()
|
||||
return names
|
||||
|
||||
|
||||
class AccessCompiler(compiler.SQLCompiler):
|
||||
extract_map = compiler.SQLCompiler.extract_map.copy()
|
||||
extract_map.update ({
|
||||
'month': 'm',
|
||||
'day': 'd',
|
||||
'year': 'yyyy',
|
||||
'second': 's',
|
||||
'hour': 'h',
|
||||
'doy': 'y',
|
||||
'minute': 'n',
|
||||
'quarter': 'q',
|
||||
'dow': 'w',
|
||||
'week': 'ww'
|
||||
})
|
||||
|
||||
def visit_select_precolumns(self, select):
|
||||
"""Access puts TOP, it's version of LIMIT here """
|
||||
s = select.distinct and "DISTINCT " or ""
|
||||
if select.limit:
|
||||
s += "TOP %s " % (select.limit)
|
||||
if select.offset:
|
||||
raise exc.InvalidRequestError(
|
||||
'Access does not support LIMIT with an offset')
|
||||
return s
|
||||
|
||||
def limit_clause(self, select):
|
||||
"""Limit in access is after the select keyword"""
|
||||
return ""
|
||||
|
||||
def binary_operator_string(self, binary):
|
||||
"""Access uses "mod" instead of "%" """
|
||||
return binary.operator == '%' and 'mod' or binary.operator
|
||||
|
||||
def label_select_column(self, select, column, asfrom):
|
||||
if isinstance(column, expression.Function):
|
||||
return column.label()
|
||||
else:
|
||||
return super(AccessCompiler, self).\
|
||||
label_select_column(select, column, asfrom)
|
||||
|
||||
function_rewrites = {'current_date': 'now',
|
||||
'current_timestamp': 'now',
|
||||
'length': 'len',
|
||||
}
|
||||
def visit_function(self, func):
|
||||
"""Access function names differ from the ANSI SQL names;
|
||||
rewrite common ones"""
|
||||
func.name = self.function_rewrites.get(func.name, func.name)
|
||||
return super(AccessCompiler, self).visit_function(func)
|
||||
|
||||
def for_update_clause(self, select):
|
||||
"""FOR UPDATE is not supported by Access; silently ignore"""
|
||||
return ''
|
||||
|
||||
# Strip schema
|
||||
def visit_table(self, table, asfrom=False, **kwargs):
|
||||
if asfrom:
|
||||
return self.preparer.quote(table.name, table.quote)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def visit_join(self, join, asfrom=False, **kwargs):
|
||||
return (self.process(join.left, asfrom=True) + \
|
||||
(join.isouter and " LEFT OUTER JOIN " or " INNER JOIN ") + \
|
||||
self.process(join.right, asfrom=True) + " ON " + \
|
||||
self.process(join.onclause))
|
||||
|
||||
def visit_extract(self, extract, **kw):
|
||||
field = self.extract_map.get(extract.field, extract.field)
|
||||
return 'DATEPART("%s", %s)' % \
|
||||
(field, self.process(extract.expr, **kw))
|
||||
|
||||
class AccessDDLCompiler(compiler.DDLCompiler):
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
colspec = self.preparer.format_column(column) + " " + \
|
||||
column.type.dialect_impl(self.dialect).get_col_spec()
|
||||
|
||||
# install a sequence if we have an implicit IDENTITY column
|
||||
if (not getattr(column.table, 'has_sequence', False)) and \
|
||||
column.primary_key and \
|
||||
column.autoincrement and \
|
||||
isinstance(column.type, types.Integer) and \
|
||||
not column.foreign_keys:
|
||||
if column.default is None or \
|
||||
(isinstance(column.default, schema.Sequence) and
|
||||
column.default.optional):
|
||||
column.sequence = schema.Sequence(column.name + '_seq')
|
||||
|
||||
if not column.nullable:
|
||||
colspec += " NOT NULL"
|
||||
|
||||
if hasattr(column, 'sequence'):
|
||||
column.table.has_sequence = column
|
||||
colspec = self.preparer.format_column(column) + " counter"
|
||||
else:
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
return colspec
|
||||
|
||||
def visit_drop_index(self, drop):
|
||||
index = drop.element
|
||||
self.append("\nDROP INDEX [%s].[%s]" % \
|
||||
(index.table.name,
|
||||
self._index_identifier(index.name)))
|
||||
|
||||
class AccessIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
reserved_words = compiler.RESERVED_WORDS.copy()
|
||||
reserved_words.update(['value', 'text'])
|
||||
def __init__(self, dialect):
|
||||
super(AccessIdentifierPreparer, self).\
|
||||
__init__(dialect, initial_quote='[', final_quote=']')
|
||||
|
||||
|
||||
dialect = AccessDialect
|
||||
dialect.poolclass = pool.SingletonThreadPool
|
||||
dialect.statement_compiler = AccessCompiler
|
||||
dialect.ddlcompiler = AccessDDLCompiler
|
||||
dialect.preparer = AccessIdentifierPreparer
|
||||
dialect.execution_ctx_cls = AccessExecutionContext
|
||||
@@ -1,12 +1,15 @@
|
||||
# drizzle/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2010-2011 Monty Taylor <mordred@inaugust.com>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
|
||||
"""Support for the Drizzle database.
|
||||
"""
|
||||
|
||||
.. dialect:: drizzle
|
||||
:name: Drizzle
|
||||
|
||||
Drizzle is a variant of MySQL. Unlike MySQL, Drizzle's default storage engine
|
||||
is InnoDB (transactions, foreign-keys) rather than MyISAM. For more
|
||||
@@ -16,10 +19,6 @@ the `Drizzle Documentation <http://docs.drizzle.org/index.html>`_.
|
||||
The SQLAlchemy Drizzle dialect leans heavily on the MySQL dialect, so much of
|
||||
the :doc:`SQLAlchemy MySQL <mysql>` documentation is also relevant.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
See the individual driver sections below for details on connecting.
|
||||
|
||||
"""
|
||||
|
||||
@@ -183,7 +182,7 @@ class BIGINT(sqltypes.BIGINT):
|
||||
super(BIGINT, self).__init__(**kw)
|
||||
|
||||
|
||||
class _DrizzleTime(mysql_dialect._MSTime):
|
||||
class TIME(mysql_dialect.TIME):
|
||||
"""Drizzle TIME type."""
|
||||
|
||||
|
||||
@@ -317,7 +316,7 @@ class _DrizzleBoolean(sqltypes.Boolean):
|
||||
colspecs = {
|
||||
sqltypes.Numeric: NUMERIC,
|
||||
sqltypes.Float: FLOAT,
|
||||
sqltypes.Time: _DrizzleTime,
|
||||
sqltypes.Time: TIME,
|
||||
sqltypes.Enum: ENUM,
|
||||
sqltypes.Boolean: _DrizzleBoolean,
|
||||
}
|
||||
@@ -418,6 +417,7 @@ class DrizzleIdentifierPreparer(mysql_dialect.MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
@log.class_logger
|
||||
class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
"""Details of the Drizzle dialect.
|
||||
|
||||
@@ -447,16 +447,6 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
conn.autocommit(False)
|
||||
return connect
|
||||
|
||||
def do_commit(self, connection):
|
||||
"""Execute a COMMIT."""
|
||||
|
||||
connection.commit()
|
||||
|
||||
def do_rollback(self, connection):
|
||||
"""Execute a ROLLBACK."""
|
||||
|
||||
connection.rollback()
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
"""Return a Unicode SHOW TABLES from a given schema."""
|
||||
@@ -506,4 +496,3 @@ class DrizzleDialect(mysql_dialect.MySQLDialect):
|
||||
self._backslash_escapes = False
|
||||
|
||||
|
||||
log.class_logger(DrizzleDialect)
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
"""Support for the Drizzle database via the mysql-python adapter.
|
||||
"""
|
||||
.. dialect:: drizzle+mysqldb
|
||||
:name: MySQL-Python
|
||||
:dbapi: mysqldb
|
||||
:connectstring: drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: http://sourceforge.net/projects/mysql-python
|
||||
|
||||
MySQL-Python is available at:
|
||||
|
||||
http://sourceforge.net/projects/mysql-python
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
drizzle+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# firebird/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.firebird import base, kinterbasdb
|
||||
from sqlalchemy.dialects.firebird import base, kinterbasdb, fdb
|
||||
|
||||
base.dialect = kinterbasdb.dialect
|
||||
base.dialect = fdb.dialect
|
||||
|
||||
from sqlalchemy.dialects.firebird.base import \
|
||||
SMALLINT, BIGINT, FLOAT, FLOAT, DATE, TIME, \
|
||||
@@ -18,5 +18,3 @@ __all__ = (
|
||||
'TEXT', 'NUMERIC', 'FLOAT', 'TIMESTAMP', 'VARCHAR', 'CHAR', 'BLOB',
|
||||
'dialect'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# firebird/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for the Firebird database.
|
||||
|
||||
Connectivity is usually supplied via the kinterbasdb_ DBAPI module.
|
||||
.. dialect:: firebird
|
||||
:name: Firebird
|
||||
|
||||
Dialects
|
||||
~~~~~~~~
|
||||
Firebird Dialects
|
||||
-----------------
|
||||
|
||||
Firebird offers two distinct dialects_ (not to be confused with a
|
||||
SQLAlchemy ``Dialect``):
|
||||
@@ -27,7 +27,7 @@ support for dialect 1 is not well tested and probably has
|
||||
incompatibilities.
|
||||
|
||||
Locking Behavior
|
||||
~~~~~~~~~~~~~~~~
|
||||
----------------
|
||||
|
||||
Firebird locks tables aggressively. For this reason, a DROP TABLE may
|
||||
hang until other transactions are released. SQLAlchemy does its best
|
||||
@@ -47,7 +47,7 @@ The above use case can be alleviated by calling ``first()`` on the
|
||||
all remaining cursor/connection resources.
|
||||
|
||||
RETURNING support
|
||||
~~~~~~~~~~~~~~~~~
|
||||
-----------------
|
||||
|
||||
Firebird 2.0 supports returning a result set from inserts, and 2.1
|
||||
extends that to deletes and updates. This is generically exposed by
|
||||
@@ -69,7 +69,7 @@ the SQLAlchemy ``returning()`` method, such as::
|
||||
|
||||
"""
|
||||
|
||||
import datetime, re
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import exc, types as sqltypes, sql, util
|
||||
@@ -78,9 +78,8 @@ from sqlalchemy.engine import base, default, reflection
|
||||
from sqlalchemy.sql import compiler
|
||||
|
||||
|
||||
from sqlalchemy.types import (BIGINT, BLOB, BOOLEAN, DATE,
|
||||
FLOAT, INTEGER, NUMERIC, SMALLINT,
|
||||
TEXT, TIME, TIMESTAMP)
|
||||
from sqlalchemy.types import (BIGINT, BLOB, DATE, FLOAT, INTEGER, NUMERIC,
|
||||
SMALLINT, TEXT, TIME, TIMESTAMP, Integer)
|
||||
|
||||
|
||||
RESERVED_WORDS = set([
|
||||
@@ -126,36 +125,49 @@ RESERVED_WORDS = set([
|
||||
class _StringType(sqltypes.String):
|
||||
"""Base for Firebird string types."""
|
||||
|
||||
def __init__(self, charset = None, **kw):
|
||||
def __init__(self, charset=None, **kw):
|
||||
self.charset = charset
|
||||
super(_StringType, self).__init__(**kw)
|
||||
|
||||
|
||||
class VARCHAR(_StringType, sqltypes.VARCHAR):
|
||||
"""Firebird VARCHAR type"""
|
||||
__visit_name__ = 'VARCHAR'
|
||||
|
||||
def __init__(self, length = None, **kwargs):
|
||||
def __init__(self, length=None, **kwargs):
|
||||
super(VARCHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class CHAR(_StringType, sqltypes.CHAR):
|
||||
"""Firebird CHAR type"""
|
||||
__visit_name__ = 'CHAR'
|
||||
|
||||
def __init__(self, length = None, **kwargs):
|
||||
def __init__(self, length=None, **kwargs):
|
||||
super(CHAR, self).__init__(length=length, **kwargs)
|
||||
|
||||
|
||||
class _FBDateTime(sqltypes.DateTime):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if type(value) == datetime.date:
|
||||
return datetime.datetime(value.year, value.month, value.day)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
colspecs = {
|
||||
sqltypes.DateTime: _FBDateTime
|
||||
}
|
||||
|
||||
ischema_names = {
|
||||
'SHORT': SMALLINT,
|
||||
'LONG': BIGINT,
|
||||
'LONG': INTEGER,
|
||||
'QUAD': FLOAT,
|
||||
'FLOAT': FLOAT,
|
||||
'DATE': DATE,
|
||||
'TIME': TIME,
|
||||
'TEXT': TEXT,
|
||||
'INT64': NUMERIC,
|
||||
'INT64': BIGINT,
|
||||
'DOUBLE': FLOAT,
|
||||
'TIMESTAMP': TIMESTAMP,
|
||||
'VARYING': VARCHAR,
|
||||
@@ -192,20 +204,42 @@ class FBTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._extend_string(type_, basic)
|
||||
|
||||
def visit_VARCHAR(self, type_):
|
||||
if not type_.length:
|
||||
raise exc.CompileError(
|
||||
"VARCHAR requires a length on dialect %s" %
|
||||
self.dialect.name)
|
||||
basic = super(FBTypeCompiler, self).visit_VARCHAR(type_)
|
||||
return self._extend_string(type_, basic)
|
||||
|
||||
|
||||
|
||||
class FBCompiler(sql.compiler.SQLCompiler):
|
||||
"""Firebird specific idiosyncrasies"""
|
||||
|
||||
def visit_mod(self, binary, **kw):
|
||||
# Firebird lacks a builtin modulo operator, but there is
|
||||
# an equivalent function in the ib_udf library.
|
||||
ansi_bind_rules = True
|
||||
|
||||
#def visit_contains_op_binary(self, binary, operator, **kw):
|
||||
# cant use CONTAINING b.c. it's case insensitive.
|
||||
|
||||
#def visit_notcontains_op_binary(self, binary, operator, **kw):
|
||||
# cant use NOT CONTAINING b.c. it's case insensitive.
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
|
||||
def visit_startswith_op_binary(self, binary, operator, **kw):
|
||||
return '%s STARTING WITH %s' % (
|
||||
binary.left._compiler_dispatch(self, **kw),
|
||||
binary.right._compiler_dispatch(self, **kw))
|
||||
|
||||
def visit_notstartswith_op_binary(self, binary, operator, **kw):
|
||||
return '%s NOT STARTING WITH %s' % (
|
||||
binary.left._compiler_dispatch(self, **kw),
|
||||
binary.right._compiler_dispatch(self, **kw))
|
||||
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return "mod(%s, %s)" % (
|
||||
self.process(binary.left),
|
||||
self.process(binary.right))
|
||||
self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
def visit_alias(self, alias, asfrom=False, **kwargs):
|
||||
if self.dialect._version_two:
|
||||
@@ -249,7 +283,7 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
# may require parens - see similar example in the oracle
|
||||
# dialect
|
||||
if func.clauses is not None and len(func.clauses):
|
||||
return self.process(func.clause_expr)
|
||||
return self.process(func.clause_expr, **kw)
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -267,9 +301,9 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
|
||||
result = ""
|
||||
if select._limit:
|
||||
result += "FIRST %s " % self.process(sql.literal(select._limit))
|
||||
result += "FIRST %s " % self.process(sql.literal(select._limit))
|
||||
if select._offset:
|
||||
result +="SKIP %s " % self.process(sql.literal(select._offset))
|
||||
result += "SKIP %s " % self.process(sql.literal(select._offset))
|
||||
if select._distinct:
|
||||
result += "DISTINCT "
|
||||
return result
|
||||
@@ -280,15 +314,11 @@ class FBCompiler(sql.compiler.SQLCompiler):
|
||||
return ""
|
||||
|
||||
def returning_clause(self, stmt, returning_cols):
|
||||
|
||||
columns = [
|
||||
self.process(
|
||||
self.label_select_column(None, c, asfrom=False),
|
||||
within_columns_clause=True,
|
||||
result_map=self.result_map
|
||||
)
|
||||
self._label_select_column(None, c, True, False, {})
|
||||
for c in expression._select_iterables(returning_cols)
|
||||
]
|
||||
|
||||
return 'RETURNING ' + ', '.join(columns)
|
||||
|
||||
|
||||
@@ -329,6 +359,7 @@ class FBIdentifierPreparer(sql.compiler.IdentifierPreparer):
|
||||
"""Install Firebird specific reserved words."""
|
||||
|
||||
reserved_words = RESERVED_WORDS
|
||||
illegal_initial_characters = compiler.ILLEGAL_INITIAL_CHARACTERS.union(['_'])
|
||||
|
||||
def __init__(self, dialect):
|
||||
super(FBIdentifierPreparer, self).__init__(dialect, omit_schema=True)
|
||||
@@ -444,18 +475,34 @@ class FBDialect(default.DefaultDialect):
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
# there are two queries commonly mentioned for this.
|
||||
# this one, using view_blr, is at the Firebird FAQ among other places:
|
||||
# http://www.firebirdfaq.org/faq174/
|
||||
s = """
|
||||
SELECT DISTINCT rdb$relation_name
|
||||
FROM rdb$relation_fields
|
||||
WHERE rdb$system_flag=0 AND rdb$view_context IS NULL
|
||||
select rdb$relation_name
|
||||
from rdb$relations
|
||||
where rdb$view_blr is null
|
||||
and (rdb$system_flag is null or rdb$system_flag = 0);
|
||||
"""
|
||||
|
||||
# the other query is this one. It's not clear if there's really
|
||||
# any difference between these two. This link:
|
||||
# http://www.alberton.info/firebird_sql_meta_info.html#.Ur3vXfZGni8
|
||||
# states them as interchangeable. Some discussion at [ticket:2898]
|
||||
# SELECT DISTINCT rdb$relation_name
|
||||
# FROM rdb$relation_fields
|
||||
# WHERE rdb$system_flag=0 AND rdb$view_context IS NULL
|
||||
|
||||
return [self.normalize_name(row[0]) for row in connection.execute(s)]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
# see http://www.firebirdfaq.org/faq174/
|
||||
s = """
|
||||
SELECT distinct rdb$view_name
|
||||
FROM rdb$view_relations
|
||||
select rdb$relation_name
|
||||
from rdb$relations
|
||||
where rdb$view_blr is not null
|
||||
and (rdb$system_flag is null or rdb$system_flag = 0);
|
||||
"""
|
||||
return [self.normalize_name(row[0]) for row in connection.execute(s)]
|
||||
|
||||
@@ -474,7 +521,7 @@ class FBDialect(default.DefaultDialect):
|
||||
return None
|
||||
|
||||
@reflection.cache
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
# Query to extract the PK/FK constrained fields of the given table
|
||||
keyqry = """
|
||||
SELECT se.rdb$field_name AS fname
|
||||
@@ -486,7 +533,7 @@ class FBDialect(default.DefaultDialect):
|
||||
# get primary key fields
|
||||
c = connection.execute(keyqry, ["PRIMARY KEY", tablename])
|
||||
pkfields = [self.normalize_name(r['fname']) for r in c.fetchall()]
|
||||
return pkfields
|
||||
return {'constrained_columns': pkfields, 'name': None}
|
||||
|
||||
@reflection.cache
|
||||
def get_column_sequence(self, connection,
|
||||
@@ -541,7 +588,8 @@ class FBDialect(default.DefaultDialect):
|
||||
ORDER BY r.rdb$field_position
|
||||
"""
|
||||
# get the PK, used to determine the eventual associated sequence
|
||||
pkey_cols = self.get_primary_keys(connection, table_name)
|
||||
pk_constraint = self.get_pk_constraint(connection, table_name)
|
||||
pkey_cols = pk_constraint['constrained_columns']
|
||||
|
||||
tablename = self.denormalize_name(table_name)
|
||||
# get all of the fields for this table
|
||||
@@ -561,8 +609,8 @@ class FBDialect(default.DefaultDialect):
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(colspec, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
elif colspec == 'INT64':
|
||||
coltype = coltype(
|
||||
elif issubclass(coltype, Integer) and row['fprec'] != 0:
|
||||
coltype = NUMERIC(
|
||||
precision=row['fprec'],
|
||||
scale=row['fscale'] * -1)
|
||||
elif colspec in ('VARYING', 'CSTRING'):
|
||||
@@ -593,11 +641,11 @@ class FBDialect(default.DefaultDialect):
|
||||
# Redundant
|
||||
defvalue = None
|
||||
col_d = {
|
||||
'name' : name,
|
||||
'type' : coltype,
|
||||
'nullable' : not bool(row['null_flag']),
|
||||
'default' : defvalue,
|
||||
'autoincrement':defvalue is None
|
||||
'name': name,
|
||||
'type': coltype,
|
||||
'nullable': not bool(row['null_flag']),
|
||||
'default': defvalue,
|
||||
'autoincrement': defvalue is None
|
||||
}
|
||||
|
||||
if orig_colname.lower() == orig_colname:
|
||||
@@ -605,7 +653,7 @@ class FBDialect(default.DefaultDialect):
|
||||
|
||||
# if the PK is a single field, try to see if its linked to
|
||||
# a sequence thru a trigger
|
||||
if len(pkey_cols)==1 and name==pkey_cols[0]:
|
||||
if len(pkey_cols) == 1 and name == pkey_cols[0]:
|
||||
seq_d = self.get_column_sequence(connection, tablename, name)
|
||||
if seq_d is not None:
|
||||
col_d['sequence'] = seq_d
|
||||
@@ -635,12 +683,12 @@ class FBDialect(default.DefaultDialect):
|
||||
tablename = self.denormalize_name(table_name)
|
||||
|
||||
c = connection.execute(fkqry, ["FOREIGN KEY", tablename])
|
||||
fks = util.defaultdict(lambda:{
|
||||
'name' : None,
|
||||
'constrained_columns' : [],
|
||||
'referred_schema' : None,
|
||||
'referred_table' : None,
|
||||
'referred_columns' : []
|
||||
fks = util.defaultdict(lambda: {
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': None,
|
||||
'referred_columns': []
|
||||
})
|
||||
|
||||
for row in c:
|
||||
@@ -653,7 +701,7 @@ class FBDialect(default.DefaultDialect):
|
||||
self.normalize_name(row['fname']))
|
||||
fk['referred_columns'].append(
|
||||
self.normalize_name(row['targetfname']))
|
||||
return fks.values()
|
||||
return list(fks.values())
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
@@ -669,7 +717,7 @@ class FBDialect(default.DefaultDialect):
|
||||
ic.rdb$index_name
|
||||
WHERE ix.rdb$relation_name=? AND ix.rdb$foreign_key IS NULL
|
||||
AND rdb$relation_constraints.rdb$constraint_type IS NULL
|
||||
ORDER BY index_name, field_name
|
||||
ORDER BY index_name, ic.rdb$field_position
|
||||
"""
|
||||
c = connection.execute(qry, [self.denormalize_name(table_name)])
|
||||
|
||||
@@ -684,17 +732,5 @@ class FBDialect(default.DefaultDialect):
|
||||
indexrec['column_names'].append(
|
||||
self.normalize_name(row['field_name']))
|
||||
|
||||
return indexes.values()
|
||||
return list(indexes.values())
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
# kinterbase does not accept a None, but wants an empty list
|
||||
# when there are no arguments.
|
||||
cursor.execute(statement, parameters or [])
|
||||
|
||||
def do_rollback(self, connection):
|
||||
# Use the retaining feature, that keeps the transaction going
|
||||
connection.rollback(True)
|
||||
|
||||
def do_commit(self, connection):
|
||||
# Use the retaining feature, that keeps the transaction going
|
||||
connection.commit(True)
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# firebird/fdb.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
.. dialect:: firebird+fdb
|
||||
:name: fdb
|
||||
:dbapi: pyodbc
|
||||
:connectstring: firebird+fdb://user:password@host:port/path/to/db[?key=value&key=value...]
|
||||
:url: http://pypi.python.org/pypi/fdb/
|
||||
|
||||
fdb is a kinterbasdb compatible DBAPI for Firebird.
|
||||
|
||||
.. versionadded:: 0.8 - Support for the fdb Firebird driver.
|
||||
|
||||
.. versionchanged:: 0.9 - The fdb dialect is now the default dialect
|
||||
under the ``firebird://`` URL space, as ``fdb`` is now the official
|
||||
Python driver for Firebird.
|
||||
|
||||
Arguments
|
||||
----------
|
||||
|
||||
The ``fdb`` dialect is based on the :mod:`sqlalchemy.dialects.firebird.kinterbasdb`
|
||||
dialect, however does not accept every argument that Kinterbasdb does.
|
||||
|
||||
* ``enable_rowcount`` - True by default, setting this to False disables
|
||||
the usage of "cursor.rowcount" with the
|
||||
Kinterbasdb dialect, which SQLAlchemy ordinarily calls upon automatically
|
||||
after any UPDATE or DELETE statement. When disabled, SQLAlchemy's
|
||||
ResultProxy will return -1 for result.rowcount. The rationale here is
|
||||
that Kinterbasdb requires a second round trip to the database when
|
||||
.rowcount is called - since SQLA's resultproxy automatically closes
|
||||
the cursor after a non-result-returning statement, rowcount must be
|
||||
called, if at all, before the result object is returned. Additionally,
|
||||
cursor.rowcount may not return correct results with older versions
|
||||
of Firebird, and setting this flag to False will also cause the
|
||||
SQLAlchemy ORM to ignore its usage. The behavior can also be controlled on a
|
||||
per-execution basis using the ``enable_rowcount`` option with
|
||||
:meth:`.Connection.execution_options`::
|
||||
|
||||
conn = engine.connect().execution_options(enable_rowcount=True)
|
||||
r = conn.execute(stmt)
|
||||
print r.rowcount
|
||||
|
||||
* ``retaining`` - False by default. Setting this to True will pass the
|
||||
``retaining=True`` keyword argument to the ``.commit()`` and ``.rollback()``
|
||||
methods of the DBAPI connection, which can improve performance in some
|
||||
situations, but apparently with significant caveats.
|
||||
Please read the fdb and/or kinterbasdb DBAPI documentation in order to
|
||||
understand the implications of this flag.
|
||||
|
||||
.. versionadded:: 0.8.2 - ``retaining`` keyword argument specifying
|
||||
transaction retaining behavior - in 0.8 it defaults to ``True``
|
||||
for backwards compatibility.
|
||||
|
||||
.. versionchanged:: 0.9.0 - the ``retaining`` flag defaults to ``False``.
|
||||
In 0.8 it defaulted to ``True``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
http://pythonhosted.org/fdb/usage-guide.html#retaining-transactions - information
|
||||
on the "retaining" flag.
|
||||
|
||||
"""
|
||||
|
||||
from .kinterbasdb import FBDialect_kinterbasdb
|
||||
from ... import util
|
||||
|
||||
|
||||
class FBDialect_fdb(FBDialect_kinterbasdb):
|
||||
|
||||
def __init__(self, enable_rowcount=True,
|
||||
retaining=False, **kwargs):
|
||||
super(FBDialect_fdb, self).__init__(
|
||||
enable_rowcount=enable_rowcount,
|
||||
retaining=retaining, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('fdb')
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if opts.get('port'):
|
||||
opts['host'] = "%s/%s" % (opts['host'], opts['port'])
|
||||
del opts['port']
|
||||
opts.update(url.query)
|
||||
|
||||
util.coerce_kw_type(opts, 'type_conv', int)
|
||||
|
||||
return ([], opts)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
"""Get the version of the Firebird server used by a connection.
|
||||
|
||||
Returns a tuple of (`major`, `minor`, `build`), three integers
|
||||
representing the version of the attached server.
|
||||
"""
|
||||
|
||||
# This is the simpler approach (the other uses the services api),
|
||||
# that for backward compatibility reasons returns a string like
|
||||
# LI-V6.3.3.12981 Firebird 2.0
|
||||
# where the first version is a fake one resembling the old
|
||||
# Interbase signature.
|
||||
|
||||
isc_info_firebird_version = 103
|
||||
fbconn = connection.connection
|
||||
|
||||
version = fbconn.db_info(isc_info_firebird_version)
|
||||
|
||||
return self._parse_version_info(version)
|
||||
|
||||
dialect = FBDialect_fdb
|
||||
@@ -1,58 +1,48 @@
|
||||
# firebird/kinterbasdb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
The most common way to connect to a Firebird engine is implemented by
|
||||
kinterbasdb__, currently maintained__ directly by the Firebird people.
|
||||
.. dialect:: firebird+kinterbasdb
|
||||
:name: kinterbasdb
|
||||
:dbapi: kinterbasdb
|
||||
:connectstring: firebird+kinterbasdb://user:password@host:port/path/to/db[?key=value&key=value...]
|
||||
:url: http://firebirdsql.org/index.php?op=devel&sub=python
|
||||
|
||||
The connection URL is of the form
|
||||
``firebird[+kinterbasdb]://user:password@host:port/path/to/db[?key=value&key=value...]``.
|
||||
Arguments
|
||||
----------
|
||||
|
||||
Kinterbasedb backend specific keyword arguments are:
|
||||
The Kinterbasdb backend accepts the ``enable_rowcount`` and ``retaining``
|
||||
arguments accepted by the :mod:`sqlalchemy.dialects.firebird.fdb` dialect. In addition, it
|
||||
also accepts the following:
|
||||
|
||||
* type_conv - select the kind of mapping done on the types: by default
|
||||
SQLAlchemy uses 200 with Unicode, datetime and decimal support (see
|
||||
details__).
|
||||
* ``type_conv`` - select the kind of mapping done on the types: by default
|
||||
SQLAlchemy uses 200 with Unicode, datetime and decimal support. See
|
||||
the linked documents below for further information.
|
||||
|
||||
* concurrency_level - set the backend policy with regards to threading
|
||||
issues: by default SQLAlchemy uses policy 1 (see details__).
|
||||
* ``concurrency_level`` - set the backend policy with regards to threading
|
||||
issues: by default SQLAlchemy uses policy 1. See the linked documents
|
||||
below for futher information.
|
||||
|
||||
* enable_rowcount - True by default, setting this to False disables
|
||||
the usage of "cursor.rowcount" with the
|
||||
Kinterbasdb dialect, which SQLAlchemy ordinarily calls upon automatically
|
||||
after any UPDATE or DELETE statement. When disabled, SQLAlchemy's
|
||||
ResultProxy will return -1 for result.rowcount. The rationale here is
|
||||
that Kinterbasdb requires a second round trip to the database when
|
||||
.rowcount is called - since SQLA's resultproxy automatically closes
|
||||
the cursor after a non-result-returning statement, rowcount must be
|
||||
called, if at all, before the result object is returned. Additionally,
|
||||
cursor.rowcount may not return correct results with older versions
|
||||
of Firebird, and setting this flag to False will also cause the
|
||||
SQLAlchemy ORM to ignore its usage. The behavior can also be controlled on a
|
||||
per-execution basis using the `enable_rowcount` option with
|
||||
:meth:`execution_options()`::
|
||||
.. seealso::
|
||||
|
||||
conn = engine.connect().execution_options(enable_rowcount=True)
|
||||
r = conn.execute(stmt)
|
||||
print r.rowcount
|
||||
http://sourceforge.net/projects/kinterbasdb
|
||||
|
||||
http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_param_conv_dynamic_type_translation
|
||||
|
||||
http://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurrency
|
||||
|
||||
__ http://sourceforge.net/projects/kinterbasdb
|
||||
__ http://firebirdsql.org/index.php?op=devel&sub=python
|
||||
__ http://kinterbasdb.sourceforge.net/dist_docs/usage.html#adv_param_conv_dynamic_type_translation
|
||||
__ http://kinterbasdb.sourceforge.net/dist_docs/usage.html#special_issue_concurrency
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.firebird.base import FBDialect, \
|
||||
FBCompiler, FBExecutionContext
|
||||
from sqlalchemy import util, types as sqltypes
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from .base import FBDialect, FBExecutionContext
|
||||
from ... import util, types as sqltypes
|
||||
from re import match
|
||||
import decimal
|
||||
|
||||
|
||||
class _FBNumeric_kinterbasdb(sqltypes.Numeric):
|
||||
class _kinterbasdb_numeric(object):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if isinstance(value, decimal.Decimal):
|
||||
@@ -61,6 +51,13 @@ class _FBNumeric_kinterbasdb(sqltypes.Numeric):
|
||||
return value
|
||||
return process
|
||||
|
||||
class _FBNumeric_kinterbasdb(_kinterbasdb_numeric, sqltypes.Numeric):
|
||||
pass
|
||||
|
||||
class _FBFloat_kinterbasdb(_kinterbasdb_numeric, sqltypes.Float):
|
||||
pass
|
||||
|
||||
|
||||
class FBExecutionContext_kinterbasdb(FBExecutionContext):
|
||||
@property
|
||||
def rowcount(self):
|
||||
@@ -70,6 +67,7 @@ class FBExecutionContext_kinterbasdb(FBExecutionContext):
|
||||
else:
|
||||
return -1
|
||||
|
||||
|
||||
class FBDialect_kinterbasdb(FBDialect):
|
||||
driver = 'kinterbasdb'
|
||||
supports_sane_rowcount = False
|
||||
@@ -81,24 +79,37 @@ class FBDialect_kinterbasdb(FBDialect):
|
||||
colspecs = util.update_copy(
|
||||
FBDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric:_FBNumeric_kinterbasdb,
|
||||
sqltypes.Numeric: _FBNumeric_kinterbasdb,
|
||||
sqltypes.Float: _FBFloat_kinterbasdb,
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
def __init__(self, type_conv=200, concurrency_level=1,
|
||||
enable_rowcount=True, **kwargs):
|
||||
enable_rowcount=True,
|
||||
retaining=False, **kwargs):
|
||||
super(FBDialect_kinterbasdb, self).__init__(**kwargs)
|
||||
self.enable_rowcount = enable_rowcount
|
||||
self.type_conv = type_conv
|
||||
self.concurrency_level = concurrency_level
|
||||
self.retaining = retaining
|
||||
if enable_rowcount:
|
||||
self.supports_sane_rowcount = True
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
k = __import__('kinterbasdb')
|
||||
return k
|
||||
return __import__('kinterbasdb')
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
# kinterbase does not accept a None, but wants an empty list
|
||||
# when there are no arguments.
|
||||
cursor.execute(statement, parameters or [])
|
||||
|
||||
def do_rollback(self, dbapi_connection):
|
||||
dbapi_connection.rollback(self.retaining)
|
||||
|
||||
def do_commit(self, dbapi_connection):
|
||||
dbapi_connection.commit(self.retaining)
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
@@ -117,7 +128,8 @@ class FBDialect_kinterbasdb(FBDialect):
|
||||
initialized = getattr(self.dbapi, 'initialized', None)
|
||||
if initialized is None:
|
||||
# CVS rev 1.96 changed the name of the attribute:
|
||||
# http://kinterbasdb.cvs.sourceforge.net/viewvc/kinterbasdb/Kinterbasdb-3.0/__init__.py?r1=1.95&r2=1.96
|
||||
# http://kinterbasdb.cvs.sourceforge.net/viewvc/kinterbasdb/
|
||||
# Kinterbasdb-3.0/__init__.py?r1=1.95&r2=1.96
|
||||
initialized = getattr(self.dbapi, '_initialized', False)
|
||||
if not initialized:
|
||||
self.dbapi.init(type_conv=type_conv,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# informix/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.informix import base, informixdb
|
||||
|
||||
base.dialect = informixdb.dialect
|
||||
@@ -1,596 +0,0 @@
|
||||
# informix/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# coding: gbk
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the Informix database.
|
||||
|
||||
.. note::
|
||||
|
||||
The Informix dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import sql, schema, exc, pool, util
|
||||
from sqlalchemy.sql import compiler, text
|
||||
from sqlalchemy.engine import default, reflection
|
||||
from sqlalchemy import types as sqltypes
|
||||
|
||||
RESERVED_WORDS = set(
|
||||
["abs", "absolute", "access", "access_method", "acos", "active", "add",
|
||||
"address", "add_months", "admin", "after", "aggregate", "alignment",
|
||||
"all", "allocate", "all_rows", "alter", "and", "ansi", "any", "append",
|
||||
"array", "as", "asc", "ascii", "asin", "at", "atan", "atan2", "attach",
|
||||
"attributes", "audit", "authentication", "authid", "authorization",
|
||||
"authorized", "auto", "autofree", "auto_reprepare", "auto_stat_mode",
|
||||
"avg", "avoid_execute", "avoid_fact", "avoid_full", "avoid_hash",
|
||||
"avoid_index", "avoid_index_sj", "avoid_multi_index", "avoid_nl",
|
||||
"avoid_star_join", "avoid_subqf", "based", "before", "begin",
|
||||
"between", "bigint", "bigserial", "binary", "bitand", "bitandnot",
|
||||
"bitnot", "bitor", "bitxor", "blob", "blobdir", "boolean", "both",
|
||||
"bound_impl_pdq", "buffered", "builtin", "by", "byte", "cache", "call",
|
||||
"cannothash", "cardinality", "cascade", "case", "cast", "ceil", "char",
|
||||
"character", "character_length", "char_length", "check", "class",
|
||||
"class_origin", "client", "clob", "clobdir", "close", "cluster",
|
||||
"clustersize", "cobol", "codeset", "collation", "collection",
|
||||
"column", "columns", "commit", "committed", "commutator", "component",
|
||||
"components", "concat", "concurrent", "connect", "connection",
|
||||
"connection_name", "connect_by_iscycle", "connect_by_isleaf",
|
||||
"connect_by_rootconst", "constraint", "constraints", "constructor",
|
||||
"context", "continue", "copy", "cos", "costfunc", "count", "crcols",
|
||||
"create", "cross", "current", "current_role", "currval", "cursor",
|
||||
"cycle", "database", "datafiles", "dataskip", "date", "datetime",
|
||||
"day", "dba", "dbdate", "dbinfo", "dbpassword", "dbsecadm",
|
||||
"dbservername", "deallocate", "debug", "debugmode", "debug_env", "dec",
|
||||
"decimal", "declare", "decode", "decrypt_binary", "decrypt_char",
|
||||
"dec_t", "default", "default_role", "deferred", "deferred_prepare",
|
||||
"define", "delay", "delete", "deleting", "delimited", "delimiter",
|
||||
"deluxe", "desc", "describe", "descriptor", "detach", "diagnostics",
|
||||
"directives", "dirty", "disable", "disabled", "disconnect", "disk",
|
||||
"distinct", "distributebinary", "distributesreferences",
|
||||
"distributions", "document", "domain", "donotdistribute", "dormant",
|
||||
"double", "drop", "dtime_t", "each", "elif", "else", "enabled",
|
||||
"encryption", "encrypt_aes", "encrypt_tdes", "end", "enum",
|
||||
"environment", "error", "escape", "exception", "exclusive", "exec",
|
||||
"execute", "executeanywhere", "exemption", "exists", "exit", "exp",
|
||||
"explain", "explicit", "express", "expression", "extdirectives",
|
||||
"extend", "extent", "external", "fact", "false", "far", "fetch",
|
||||
"file", "filetoblob", "filetoclob", "fillfactor", "filtering", "first",
|
||||
"first_rows", "fixchar", "fixed", "float", "floor", "flush", "for",
|
||||
"force", "forced", "force_ddl_exec", "foreach", "foreign", "format",
|
||||
"format_units", "fortran", "found", "fraction", "fragment",
|
||||
"fragments", "free", "from", "full", "function", "general", "get",
|
||||
"gethint", "global", "go", "goto", "grant", "greaterthan",
|
||||
"greaterthanorequal", "group", "handlesnulls", "hash", "having", "hdr",
|
||||
"hex", "high", "hint", "hold", "home", "hour", "idslbacreadarray",
|
||||
"idslbacreadset", "idslbacreadtree", "idslbacrules",
|
||||
"idslbacwritearray", "idslbacwriteset", "idslbacwritetree",
|
||||
"idssecuritylabel", "if", "ifx_auto_reprepare", "ifx_batchedread_table",
|
||||
"ifx_int8_t", "ifx_lo_create_spec_t", "ifx_lo_stat_t", "immediate",
|
||||
"implicit", "implicit_pdq", "in", "inactive", "increment", "index",
|
||||
"indexes", "index_all", "index_sj", "indicator", "informix", "init",
|
||||
"initcap", "inline", "inner", "inout", "insert", "inserting", "instead",
|
||||
"int", "int8", "integ", "integer", "internal", "internallength",
|
||||
"interval", "into", "intrvl_t", "is", "iscanonical", "isolation",
|
||||
"item", "iterator", "java", "join", "keep", "key", "label", "labeleq",
|
||||
"labelge", "labelglb", "labelgt", "labelle", "labellt", "labellub",
|
||||
"labeltostring", "language", "last", "last_day", "leading", "left",
|
||||
"length", "lessthan", "lessthanorequal", "let", "level", "like",
|
||||
"limit", "list", "listing", "load", "local", "locator", "lock", "locks",
|
||||
"locopy", "loc_t", "log", "log10", "logn", "long", "loop", "lotofile",
|
||||
"low", "lower", "lpad", "ltrim", "lvarchar", "matched", "matches",
|
||||
"max", "maxerrors", "maxlen", "maxvalue", "mdy", "median", "medium",
|
||||
"memory", "memory_resident", "merge", "message_length", "message_text",
|
||||
"middle", "min", "minute", "minvalue", "mod", "mode", "moderate",
|
||||
"modify", "module", "money", "month", "months_between", "mounting",
|
||||
"multiset", "multi_index", "name", "nchar", "negator", "new", "next",
|
||||
"nextval", "next_day", "no", "nocache", "nocycle", "nomaxvalue",
|
||||
"nomigrate", "nominvalue", "none", "non_dim", "non_resident", "noorder",
|
||||
"normal", "not", "notemplatearg", "notequal", "null", "nullif",
|
||||
"numeric", "numrows", "numtodsinterval", "numtoyminterval", "nvarchar",
|
||||
"nvl", "octet_length", "of", "off", "old", "on", "online", "only",
|
||||
"opaque", "opclass", "open", "optcompind", "optical", "optimization",
|
||||
"option", "or", "order", "ordered", "out", "outer", "output",
|
||||
"override", "page", "parallelizable", "parameter", "partition",
|
||||
"pascal", "passedbyvalue", "password", "pdqpriority", "percaltl_cos",
|
||||
"pipe", "pli", "pload", "policy", "pow", "power", "precision",
|
||||
"prepare", "previous", "primary", "prior", "private", "privileges",
|
||||
"procedure", "properties", "public", "put", "raise", "range", "raw",
|
||||
"read", "real", "recordend", "references", "referencing", "register",
|
||||
"rejectfile", "relative", "release", "remainder", "rename",
|
||||
"reoptimization", "repeatable", "replace", "replication", "reserve",
|
||||
"resolution", "resource", "restart", "restrict", "resume", "retain",
|
||||
"retainupdatelocks", "return", "returned_sqlstate", "returning",
|
||||
"returns", "reuse", "revoke", "right", "robin", "role", "rollback",
|
||||
"rollforward", "root", "round", "routine", "row", "rowid", "rowids",
|
||||
"rows", "row_count", "rpad", "rtrim", "rule", "sameas", "samples",
|
||||
"sampling", "save", "savepoint", "schema", "scroll", "seclabel_by_comp",
|
||||
"seclabel_by_name", "seclabel_to_char", "second", "secondary",
|
||||
"section", "secured", "security", "selconst", "select", "selecting",
|
||||
"selfunc", "selfuncargs", "sequence", "serial", "serial8",
|
||||
"serializable", "serveruuid", "server_name", "session", "set",
|
||||
"setsessionauth", "share", "short", "siblings", "signed", "sin",
|
||||
"sitename", "size", "skall", "skinhibit", "skip", "skshow",
|
||||
"smallfloat", "smallint", "some", "specific", "sql", "sqlcode",
|
||||
"sqlcontext", "sqlerror", "sqlstate", "sqlwarning", "sqrt",
|
||||
"stability", "stack", "standard", "start", "star_join", "statchange",
|
||||
"statement", "static", "statistics", "statlevel", "status", "stdev",
|
||||
"step", "stop", "storage", "store", "strategies", "string",
|
||||
"stringtolabel", "struct", "style", "subclass_origin", "substr",
|
||||
"substring", "sum", "support", "sync", "synonym", "sysdate",
|
||||
"sysdbclose", "sysdbopen", "system", "sys_connect_by_path", "table",
|
||||
"tables", "tan", "task", "temp", "template", "test", "text", "then",
|
||||
"time", "timeout", "to", "today", "to_char", "to_date",
|
||||
"to_dsinterval", "to_number", "to_yminterval", "trace", "trailing",
|
||||
"transaction", "transition", "tree", "trigger", "triggers", "trim",
|
||||
"true", "trunc", "truncate", "trusted", "type", "typedef", "typeid",
|
||||
"typename", "typeof", "uid", "uncommitted", "under", "union",
|
||||
"unique", "units", "unknown", "unload", "unlock", "unsigned",
|
||||
"update", "updating", "upon", "upper", "usage", "use",
|
||||
"uselastcommitted", "user", "use_hash", "use_nl", "use_subqf",
|
||||
"using", "value", "values", "var", "varchar", "variable", "variance",
|
||||
"variant", "varying", "vercols", "view", "violations", "void",
|
||||
"volatile", "wait", "warning", "weekday", "when", "whenever", "where",
|
||||
"while", "with", "without", "work", "write", "writedown", "writeup",
|
||||
"xadatasource", "xid", "xload", "xunload", "year"
|
||||
])
|
||||
|
||||
class InfoDateTime(sqltypes.DateTime):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
if value.microsecond:
|
||||
value = value.replace(microsecond=0)
|
||||
return value
|
||||
return process
|
||||
|
||||
class InfoTime(sqltypes.Time):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
if value.microsecond:
|
||||
value = value.replace(microsecond=0)
|
||||
return value
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value.time()
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
colspecs = {
|
||||
sqltypes.DateTime : InfoDateTime,
|
||||
sqltypes.TIMESTAMP: InfoDateTime,
|
||||
sqltypes.Time: InfoTime,
|
||||
}
|
||||
|
||||
|
||||
ischema_names = {
|
||||
0 : sqltypes.CHAR, # CHAR
|
||||
1 : sqltypes.SMALLINT, # SMALLINT
|
||||
2 : sqltypes.INTEGER, # INT
|
||||
3 : sqltypes.FLOAT, # Float
|
||||
3 : sqltypes.Float, # SmallFloat
|
||||
5 : sqltypes.DECIMAL, # DECIMAL
|
||||
6 : sqltypes.Integer, # Serial
|
||||
7 : sqltypes.DATE, # DATE
|
||||
8 : sqltypes.Numeric, # MONEY
|
||||
10 : sqltypes.DATETIME, # DATETIME
|
||||
11 : sqltypes.LargeBinary, # BYTE
|
||||
12 : sqltypes.TEXT, # TEXT
|
||||
13 : sqltypes.VARCHAR, # VARCHAR
|
||||
15 : sqltypes.NCHAR, # NCHAR
|
||||
16 : sqltypes.NVARCHAR, # NVARCHAR
|
||||
17 : sqltypes.Integer, # INT8
|
||||
18 : sqltypes.Integer, # Serial8
|
||||
43 : sqltypes.String, # LVARCHAR
|
||||
-1 : sqltypes.BLOB, # BLOB
|
||||
-1 : sqltypes.CLOB, # CLOB
|
||||
}
|
||||
|
||||
|
||||
class InfoTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_DATETIME(self, type_):
|
||||
return "DATETIME YEAR TO SECOND"
|
||||
|
||||
def visit_TIME(self, type_):
|
||||
return "DATETIME HOUR TO SECOND"
|
||||
|
||||
def visit_TIMESTAMP(self, type_):
|
||||
return "DATETIME YEAR TO SECOND"
|
||||
|
||||
def visit_large_binary(self, type_):
|
||||
return "BYTE"
|
||||
|
||||
def visit_boolean(self, type_):
|
||||
return "SMALLINT"
|
||||
|
||||
class InfoSQLCompiler(compiler.SQLCompiler):
|
||||
def default_from(self):
|
||||
return " from systables where tabname = 'systables' "
|
||||
|
||||
def get_select_precolumns(self, select):
|
||||
s = ""
|
||||
if select._offset:
|
||||
s += "SKIP %s " % select._offset
|
||||
if select._limit:
|
||||
s += "FIRST %s " % select._limit
|
||||
s += select._distinct and "DISTINCT " or ""
|
||||
return s
|
||||
|
||||
def visit_select(self, select, asfrom=False, parens=True, **kw):
|
||||
text = compiler.SQLCompiler.visit_select(self, select, asfrom, parens, **kw)
|
||||
if asfrom and parens and self.dialect.server_version_info < (11,):
|
||||
#assuming that 11 version doesn't need this, not tested
|
||||
return "table(multiset" + text + ")"
|
||||
else:
|
||||
return text
|
||||
|
||||
def limit_clause(self, select):
|
||||
return ""
|
||||
|
||||
def visit_function(self, func, **kw):
|
||||
if func.name.lower() == 'current_date':
|
||||
return "today"
|
||||
elif func.name.lower() == 'current_time':
|
||||
return "CURRENT HOUR TO SECOND"
|
||||
elif func.name.lower() in ('current_timestamp', 'now'):
|
||||
return "CURRENT YEAR TO SECOND"
|
||||
else:
|
||||
return compiler.SQLCompiler.visit_function(self, func, **kw)
|
||||
|
||||
def visit_mod(self, binary, **kw):
|
||||
return "MOD(%s, %s)" % (self.process(binary.left), self.process(binary.right))
|
||||
|
||||
|
||||
class InfoDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
def visit_add_constraint(self, create):
|
||||
preparer = self.preparer
|
||||
return "ALTER TABLE %s ADD CONSTRAINT %s" % (
|
||||
self.preparer.format_table(create.element.table),
|
||||
self.process(create.element)
|
||||
)
|
||||
|
||||
def get_column_specification(self, column, **kw):
|
||||
colspec = self.preparer.format_column(column)
|
||||
first = None
|
||||
if column.primary_key and column.autoincrement:
|
||||
try:
|
||||
first = [c for c in column.table.primary_key.columns
|
||||
if (c.autoincrement and
|
||||
isinstance(c.type, sqltypes.Integer) and
|
||||
not c.foreign_keys)].pop(0)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if column is first:
|
||||
colspec += " SERIAL"
|
||||
else:
|
||||
colspec += " " + self.dialect.type_compiler.process(column.type)
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
if not column.nullable:
|
||||
colspec += " NOT NULL"
|
||||
|
||||
return colspec
|
||||
|
||||
def get_column_default_string(self, column):
|
||||
if (isinstance(column.server_default, schema.DefaultClause) and
|
||||
isinstance(column.server_default.arg, basestring)):
|
||||
if isinstance(column.type, (sqltypes.Integer, sqltypes.Numeric)):
|
||||
return self.sql_compiler.process(text(column.server_default.arg))
|
||||
|
||||
return super(InfoDDLCompiler, self).get_column_default_string(column)
|
||||
|
||||
### Informix wants the constraint name at the end, hence this ist c&p from sql/compiler.py
|
||||
def visit_primary_key_constraint(self, constraint):
|
||||
if len(constraint) == 0:
|
||||
return ''
|
||||
text = "PRIMARY KEY "
|
||||
text += "(%s)" % ', '.join(self.preparer.quote(c.name, c.quote)
|
||||
for c in constraint)
|
||||
text += self.define_constraint_deferrability(constraint)
|
||||
|
||||
if constraint.name is not None:
|
||||
text += " CONSTRAINT %s" % self.preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
def visit_foreign_key_constraint(self, constraint):
|
||||
preparer = self.dialect.identifier_preparer
|
||||
remote_table = list(constraint._elements.values())[0].column.table
|
||||
text = "FOREIGN KEY (%s) REFERENCES %s (%s)" % (
|
||||
', '.join(preparer.quote(f.parent.name, f.parent.quote)
|
||||
for f in constraint._elements.values()),
|
||||
preparer.format_table(remote_table),
|
||||
', '.join(preparer.quote(f.column.name, f.column.quote)
|
||||
for f in constraint._elements.values())
|
||||
)
|
||||
text += self.define_constraint_cascades(constraint)
|
||||
text += self.define_constraint_deferrability(constraint)
|
||||
|
||||
if constraint.name is not None:
|
||||
text += " CONSTRAINT %s " % \
|
||||
preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
def visit_unique_constraint(self, constraint):
|
||||
text = "UNIQUE (%s)" % (', '.join(self.preparer.quote(c.name, c.quote) for c in constraint))
|
||||
text += self.define_constraint_deferrability(constraint)
|
||||
|
||||
if constraint.name is not None:
|
||||
text += "CONSTRAINT %s " % self.preparer.format_constraint(constraint)
|
||||
return text
|
||||
|
||||
class InformixIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
|
||||
class InformixDialect(default.DefaultDialect):
|
||||
name = 'informix'
|
||||
|
||||
max_identifier_length = 128 # adjusts at runtime based on server version
|
||||
|
||||
type_compiler = InfoTypeCompiler
|
||||
statement_compiler = InfoSQLCompiler
|
||||
ddl_compiler = InfoDDLCompiler
|
||||
colspecs = colspecs
|
||||
ischema_names = ischema_names
|
||||
preparer = InformixIdentifierPreparer
|
||||
default_paramstyle = 'qmark'
|
||||
|
||||
def __init__(self, has_transactions=True, *args, **kwargs):
|
||||
self.has_transactions = has_transactions
|
||||
default.DefaultDialect.__init__(self, *args, **kwargs)
|
||||
|
||||
def initialize(self, connection):
|
||||
super(InformixDialect, self).initialize(connection)
|
||||
|
||||
# http://www.querix.com/support/knowledge-base/error_number_message/error_200
|
||||
if self.server_version_info < (9, 2):
|
||||
self.max_identifier_length = 18
|
||||
else:
|
||||
self.max_identifier_length = 128
|
||||
|
||||
def do_begin(self, connection):
|
||||
cu = connection.cursor()
|
||||
cu.execute('SET LOCK MODE TO WAIT')
|
||||
if self.has_transactions:
|
||||
cu.execute('SET ISOLATION TO REPEATABLE READ')
|
||||
|
||||
def do_commit(self, connection):
|
||||
if self.has_transactions:
|
||||
connection.commit()
|
||||
|
||||
def do_rollback(self, connection):
|
||||
if self.has_transactions:
|
||||
connection.rollback()
|
||||
|
||||
def _get_table_names(self, connection, schema, type, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
s = "select tabname, owner from systables where owner=? and tabtype=?"
|
||||
return [row[0] for row in connection.execute(s, schema, type)]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
return self._get_table_names(connection, schema, 'T', **kw)
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
return self._get_table_names(connection, schema, 'V', **kw)
|
||||
|
||||
@reflection.cache
|
||||
def get_schema_names(self, connection, **kw):
|
||||
s = "select owner from systables"
|
||||
return [row[0] for row in connection.execute(s)]
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
schema = schema or self.default_schema_name
|
||||
cursor = connection.execute(
|
||||
"""select tabname from systables where tabname=? and owner=?""",
|
||||
table_name, schema)
|
||||
return cursor.first() is not None
|
||||
|
||||
@reflection.cache
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
c = connection.execute(
|
||||
"""select colname, coltype, collength, t3.default, t1.colno from
|
||||
syscolumns as t1 , systables as t2 , OUTER sysdefaults as t3
|
||||
where t1.tabid = t2.tabid and t2.tabname=? and t2.owner=?
|
||||
and t3.tabid = t2.tabid and t3.colno = t1.colno
|
||||
order by t1.colno""", table_name, schema)
|
||||
|
||||
primary_cols = self.get_primary_keys(connection, table_name, schema, **kw)
|
||||
|
||||
columns = []
|
||||
rows = c.fetchall()
|
||||
for name, colattr, collength, default, colno in rows:
|
||||
name = name.lower()
|
||||
|
||||
autoincrement = False
|
||||
primary_key = False
|
||||
|
||||
if name in primary_cols:
|
||||
primary_key = True
|
||||
|
||||
# in 7.31, coltype = 0x000
|
||||
# ^^-- column type
|
||||
# ^-- 1 not null, 0 null
|
||||
not_nullable, coltype = divmod(colattr, 256)
|
||||
if coltype not in (0, 13) and default:
|
||||
default = default.split()[-1]
|
||||
|
||||
if coltype == 6: # Serial, mark as autoincrement
|
||||
autoincrement = True
|
||||
|
||||
if coltype == 0 or coltype == 13: # char, varchar
|
||||
coltype = ischema_names[coltype](collength)
|
||||
if default:
|
||||
default = "'%s'" % default
|
||||
elif coltype == 5: # decimal
|
||||
precision, scale = (collength & 0xFF00) >> 8, collength & 0xFF
|
||||
if scale == 255:
|
||||
scale = 0
|
||||
coltype = sqltypes.Numeric(precision, scale)
|
||||
else:
|
||||
try:
|
||||
coltype = ischema_names[coltype]
|
||||
except KeyError:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(coltype, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
|
||||
column_info = dict(name=name, type=coltype, nullable=not not_nullable,
|
||||
default=default, autoincrement=autoincrement,
|
||||
primary_key=primary_key)
|
||||
columns.append(column_info)
|
||||
return columns
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
schema_sel = schema or self.default_schema_name
|
||||
c = connection.execute(
|
||||
"""select t1.constrname as cons_name,
|
||||
t4.colname as local_column, t7.tabname as remote_table,
|
||||
t6.colname as remote_column, t7.owner as remote_owner
|
||||
from sysconstraints as t1 , systables as t2 ,
|
||||
sysindexes as t3 , syscolumns as t4 ,
|
||||
sysreferences as t5 , syscolumns as t6 , systables as t7 ,
|
||||
sysconstraints as t8 , sysindexes as t9
|
||||
where t1.tabid = t2.tabid and t2.tabname=? and t2.owner=? and t1.constrtype = 'R'
|
||||
and t3.tabid = t2.tabid and t3.idxname = t1.idxname
|
||||
and t4.tabid = t2.tabid and t4.colno in (t3.part1, t3.part2, t3.part3,
|
||||
t3.part4, t3.part5, t3.part6, t3.part7, t3.part8, t3.part9, t3.part10,
|
||||
t3.part11, t3.part11, t3.part12, t3.part13, t3.part4, t3.part15, t3.part16)
|
||||
and t5.constrid = t1.constrid and t8.constrid = t5.primary
|
||||
and t6.tabid = t5.ptabid and t6.colno in (t9.part1, t9.part2, t9.part3,
|
||||
t9.part4, t9.part5, t9.part6, t9.part7, t9.part8, t9.part9, t9.part10,
|
||||
t9.part11, t9.part11, t9.part12, t9.part13, t9.part4, t9.part15, t9.part16) and t9.idxname =
|
||||
t8.idxname
|
||||
and t7.tabid = t5.ptabid""", table_name, schema_sel)
|
||||
|
||||
|
||||
def fkey_rec():
|
||||
return {
|
||||
'name' : None,
|
||||
'constrained_columns' : [],
|
||||
'referred_schema' : None,
|
||||
'referred_table' : None,
|
||||
'referred_columns' : []
|
||||
}
|
||||
|
||||
fkeys = util.defaultdict(fkey_rec)
|
||||
|
||||
rows = c.fetchall()
|
||||
for cons_name, local_column, \
|
||||
remote_table, remote_column, remote_owner in rows:
|
||||
|
||||
rec = fkeys[cons_name]
|
||||
rec['name'] = cons_name
|
||||
local_cols, remote_cols = \
|
||||
rec['constrained_columns'], rec['referred_columns']
|
||||
|
||||
if not rec['referred_table']:
|
||||
rec['referred_table'] = remote_table
|
||||
if schema is not None:
|
||||
rec['referred_schema'] = remote_owner
|
||||
|
||||
if local_column not in local_cols:
|
||||
local_cols.append(local_column)
|
||||
if remote_column not in remote_cols:
|
||||
remote_cols.append(remote_column)
|
||||
|
||||
return fkeys.values()
|
||||
|
||||
@reflection.cache
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
|
||||
# Select the column positions from sysindexes for sysconstraints
|
||||
data = connection.execute(
|
||||
"""select t2.*
|
||||
from systables as t1, sysindexes as t2, sysconstraints as t3
|
||||
where t1.tabid=t2.tabid and t1.tabname=? and t1.owner=?
|
||||
and t2.idxname=t3.idxname and t3.constrtype='P'""",
|
||||
table_name, schema
|
||||
).fetchall()
|
||||
|
||||
colpositions = set()
|
||||
|
||||
for row in data:
|
||||
colpos = set([getattr(row, 'part%d' % x) for x in range(1,16)])
|
||||
colpositions |= colpos
|
||||
|
||||
if not len(colpositions):
|
||||
return []
|
||||
|
||||
# Select the column names using the columnpositions
|
||||
# TODO: Maybe cache a bit of those col infos (eg select all colnames for one table)
|
||||
place_holder = ','.join('?'*len(colpositions))
|
||||
c = connection.execute(
|
||||
"""select t1.colname
|
||||
from syscolumns as t1, systables as t2
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
t1.colno in (%s)""" % place_holder,
|
||||
table_name, *colpositions
|
||||
).fetchall()
|
||||
|
||||
return reduce(lambda x,y: list(x)+list(y), c, [])
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema, **kw):
|
||||
# TODO: schema...
|
||||
c = connection.execute(
|
||||
"""select t1.*
|
||||
from sysindexes as t1 , systables as t2
|
||||
where t1.tabid = t2.tabid and t2.tabname=?""",
|
||||
table_name)
|
||||
|
||||
indexes = []
|
||||
for row in c.fetchall():
|
||||
colnames = [getattr(row, 'part%d' % x) for x in range(1,16)]
|
||||
colnames = [x for x in colnames if x]
|
||||
place_holder = ','.join('?'*len(colnames))
|
||||
c = connection.execute(
|
||||
"""select t1.colname
|
||||
from syscolumns as t1, systables as t2
|
||||
where t2.tabname=? and t1.tabid = t2.tabid and
|
||||
t1.colno in (%s)""" % place_holder,
|
||||
table_name, *colnames
|
||||
).fetchall()
|
||||
c = reduce(lambda x,y: list(x)+list(y), c, [])
|
||||
indexes.append({
|
||||
'name': row.idxname,
|
||||
'unique': row.idxtype.lower() == 'u',
|
||||
'column_names': c
|
||||
})
|
||||
return indexes
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
schema = schema or self.default_schema_name
|
||||
c = connection.execute(
|
||||
"""select t1.viewtext
|
||||
from sysviews as t1 , systables as t2
|
||||
where t1.tabid=t2.tabid and t2.tabname=?
|
||||
and t2.owner=? order by seqno""",
|
||||
view_name, schema).fetchall()
|
||||
|
||||
return ''.join([row[0] for row in c])
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
return connection.execute('select CURRENT_ROLE from systables').scalar()
|
||||
@@ -1,73 +0,0 @@
|
||||
# informix/informixdb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for the informixdb DBAPI.
|
||||
|
||||
informixdb is available at:
|
||||
|
||||
http://informixdb.sourceforge.net/
|
||||
|
||||
Connecting
|
||||
^^^^^^^^^^
|
||||
|
||||
Sample informix connection::
|
||||
|
||||
engine = create_engine('informix+informixdb://user:password@host/dbname')
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy.dialects.informix.base import InformixDialect
|
||||
from sqlalchemy.engine import default
|
||||
|
||||
VERSION_RE = re.compile(r'(\d+)\.(\d+)(.+\d+)')
|
||||
|
||||
class InformixExecutionContext_informixdb(default.DefaultExecutionContext):
|
||||
def post_exec(self):
|
||||
if self.isinsert:
|
||||
self._lastrowid = self.cursor.sqlerrd[1]
|
||||
|
||||
def get_lastrowid(self):
|
||||
return self._lastrowid
|
||||
|
||||
|
||||
class InformixDialect_informixdb(InformixDialect):
|
||||
driver = 'informixdb'
|
||||
execution_ctx_cls = InformixExecutionContext_informixdb
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('informixdb')
|
||||
|
||||
def create_connect_args(self, url):
|
||||
if url.host:
|
||||
dsn = '%s@%s' % (url.database, url.host)
|
||||
else:
|
||||
dsn = url.database
|
||||
|
||||
if url.username:
|
||||
opt = {'user': url.username, 'password': url.password}
|
||||
else:
|
||||
opt = {}
|
||||
|
||||
return ([dsn], opt)
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
# http://informixdb.sourceforge.net/manual.html#inspecting-version-numbers
|
||||
v = VERSION_RE.split(connection.connection.dbms_version)
|
||||
return (int(v[1]), int(v[2]), v[3])
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
return 'closed the connection' in str(e) \
|
||||
or 'connection not open' in str(e)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
dialect = InformixDialect_informixdb
|
||||
@@ -1,9 +0,0 @@
|
||||
# maxdb/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.maxdb import base, sapdb
|
||||
|
||||
base.dialect = sapdb.dialect
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
# maxdb/sapdb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.maxdb.base import MaxDBDialect
|
||||
|
||||
class MaxDBDialect_sapdb(MaxDBDialect):
|
||||
driver = 'sapdb'
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
from sapdb import dbapi as _dbapi
|
||||
return _dbapi
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
opts.update(url.query)
|
||||
return [], opts
|
||||
|
||||
|
||||
dialect = MaxDBDialect_sapdb
|
||||
@@ -1,5 +1,5 @@
|
||||
# mssql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -23,4 +23,4 @@ __all__ = (
|
||||
'DATETIME2', 'DATETIMEOFFSET', 'DATE', 'TIME', 'SMALLDATETIME',
|
||||
'BINARY', 'VARBINARY', 'BIT', 'REAL', 'IMAGE', 'TIMESTAMP',
|
||||
'MONEY', 'SMALLMONEY', 'UNIQUEIDENTIFIER', 'SQL_VARIANT', 'dialect'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
# mssql/adodbapi.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
The adodbapi dialect is not implemented for 0.6 at this time.
|
||||
.. dialect:: mssql+adodbapi
|
||||
:name: adodbapi
|
||||
:dbapi: adodbapi
|
||||
:connectstring: mssql+adodbapi://<username>:<password>@<dsnname>
|
||||
:url: http://adodbapi.sourceforge.net/
|
||||
|
||||
.. note::
|
||||
|
||||
The adodbapi dialect is not implemented SQLAlchemy versions 0.6 and
|
||||
above at this time.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
@@ -13,6 +22,7 @@ from sqlalchemy import types as sqltypes, util
|
||||
from sqlalchemy.dialects.mssql.base import MSDateTime, MSDialect
|
||||
import sys
|
||||
|
||||
|
||||
class MSDateTime_adodbapi(MSDateTime):
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
@@ -40,7 +50,7 @@ class MSDialect_adodbapi(MSDialect):
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.DateTime:MSDateTime_adodbapi
|
||||
sqltypes.DateTime: MSDateTime_adodbapi
|
||||
}
|
||||
)
|
||||
|
||||
@@ -49,18 +59,18 @@ class MSDialect_adodbapi(MSDialect):
|
||||
|
||||
connectors = ["Provider=SQLOLEDB"]
|
||||
if 'port' in keys:
|
||||
connectors.append ("Data Source=%s, %s" %
|
||||
connectors.append("Data Source=%s, %s" %
|
||||
(keys.get("host"), keys.get("port")))
|
||||
else:
|
||||
connectors.append ("Data Source=%s" % keys.get("host"))
|
||||
connectors.append ("Initial Catalog=%s" % keys.get("database"))
|
||||
connectors.append("Data Source=%s" % keys.get("host"))
|
||||
connectors.append("Initial Catalog=%s" % keys.get("database"))
|
||||
user = keys.get("user")
|
||||
if user:
|
||||
connectors.append("User Id=%s" % user)
|
||||
connectors.append("Password=%s" % keys.get("password", ""))
|
||||
else:
|
||||
connectors.append("Integrated Security=SSPI")
|
||||
return [[";".join (connectors)], {}]
|
||||
return [[";".join(connectors)], {}]
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
return isinstance(e, self.dbapi.adodbapi.DatabaseError) and \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,17 @@
|
||||
# mssql/information_schema.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
# TODO: should be using the sys. catalog with SQL Server, not information schema
|
||||
|
||||
from sqlalchemy import Table, MetaData, Column
|
||||
from sqlalchemy.types import String, Unicode, Integer, TypeDecorator
|
||||
from ... import Table, MetaData, Column
|
||||
from ...types import String, Unicode, UnicodeText, Integer, TypeDecorator
|
||||
from ... import cast
|
||||
from ... import util
|
||||
from ...sql import expression
|
||||
from ...ext.compiler import compiles
|
||||
|
||||
ischema = MetaData()
|
||||
|
||||
@@ -15,12 +19,25 @@ class CoerceUnicode(TypeDecorator):
|
||||
impl = Unicode
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
# Py2K
|
||||
if isinstance(value, str):
|
||||
if util.py2k and isinstance(value, util.binary_type):
|
||||
value = value.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
return value
|
||||
|
||||
def bind_expression(self, bindvalue):
|
||||
return _cast_on_2005(bindvalue)
|
||||
|
||||
class _cast_on_2005(expression.ColumnElement):
|
||||
def __init__(self, bindvalue):
|
||||
self.bindvalue = bindvalue
|
||||
|
||||
@compiles(_cast_on_2005)
|
||||
def _compile(element, compiler, **kw):
|
||||
from . import base
|
||||
if compiler.dialect.server_version_info < base.MS_2005_VERSION:
|
||||
return compiler.process(element.bindvalue, **kw)
|
||||
else:
|
||||
return compiler.process(cast(element.bindvalue, Unicode), **kw)
|
||||
|
||||
schemata = Table("SCHEMATA", ischema,
|
||||
Column("CATALOG_NAME", CoerceUnicode, key="catalog_name"),
|
||||
Column("SCHEMA_NAME", CoerceUnicode, key="schema_name"),
|
||||
@@ -95,4 +112,3 @@ views = Table("VIEWS", ischema,
|
||||
Column("CHECK_OPTION", String, key="check_option"),
|
||||
Column("IS_UPDATABLE", String, key="is_updatable"),
|
||||
schema="INFORMATION_SCHEMA")
|
||||
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
# mssql/mxodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for MS-SQL via mxODBC.
|
||||
|
||||
mxODBC is available at:
|
||||
|
||||
http://www.egenix.com/
|
||||
|
||||
This was tested with mxODBC 3.1.2 and the SQL Server Native
|
||||
Client connected to MSSQL 2005 and 2008 Express Editions.
|
||||
|
||||
Connecting
|
||||
~~~~~~~~~~
|
||||
|
||||
Connection is via DSN::
|
||||
|
||||
mssql+mxodbc://<username>:<password>@<dsnname>
|
||||
.. dialect:: mssql+mxodbc
|
||||
:name: mxODBC
|
||||
:dbapi: mxodbc
|
||||
:connectstring: mssql+mxodbc://<username>:<password>@<dsnname>
|
||||
:url: http://www.egenix.com/
|
||||
|
||||
Execution Modes
|
||||
~~~~~~~~~~~~~~~
|
||||
---------------
|
||||
|
||||
mxODBC features two styles of statement execution, using the
|
||||
``cursor.execute()`` and ``cursor.executedirect()`` methods (the second being
|
||||
@@ -52,14 +42,38 @@ of ``False`` will unconditionally use string-escaped parameters.
|
||||
"""
|
||||
|
||||
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.connectors.mxodbc import MxODBCConnector
|
||||
from sqlalchemy.dialects.mssql.pyodbc import MSExecutionContext_pyodbc
|
||||
from sqlalchemy.dialects.mssql.base import (MSDialect,
|
||||
from ... import types as sqltypes
|
||||
from ...connectors.mxodbc import MxODBCConnector
|
||||
from .pyodbc import MSExecutionContext_pyodbc, _MSNumeric_pyodbc
|
||||
from .base import (MSDialect,
|
||||
MSSQLStrictCompiler,
|
||||
_MSDateTime, _MSDate, TIME)
|
||||
_MSDateTime, _MSDate, _MSTime)
|
||||
|
||||
|
||||
class _MSNumeric_mxodbc(_MSNumeric_pyodbc):
|
||||
"""Include pyodbc's numeric processor.
|
||||
"""
|
||||
|
||||
|
||||
class _MSDate_mxodbc(_MSDate):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return "%s-%s-%s" % (value.year, value.month, value.day)
|
||||
else:
|
||||
return None
|
||||
return process
|
||||
|
||||
|
||||
class _MSTime_mxodbc(_MSTime):
|
||||
def bind_processor(self, dialect):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return "%s:%s:%s" % (value.hour, value.minute, value.second)
|
||||
else:
|
||||
return None
|
||||
return process
|
||||
|
||||
|
||||
class MSExecutionContext_mxodbc(MSExecutionContext_pyodbc):
|
||||
"""
|
||||
@@ -71,23 +85,27 @@ class MSExecutionContext_mxodbc(MSExecutionContext_pyodbc):
|
||||
# is really only being used in cases where OUTPUT
|
||||
# won't work.
|
||||
|
||||
|
||||
class MSDialect_mxodbc(MxODBCConnector, MSDialect):
|
||||
|
||||
# TODO: may want to use this only if FreeTDS is not in use,
|
||||
# since FreeTDS doesn't seem to use native binds.
|
||||
statement_compiler = MSSQLStrictCompiler
|
||||
# this is only needed if "native ODBC" mode is used,
|
||||
# which is now disabled by default.
|
||||
#statement_compiler = MSSQLStrictCompiler
|
||||
|
||||
execution_ctx_cls = MSExecutionContext_mxodbc
|
||||
|
||||
# flag used by _MSNumeric_mxodbc
|
||||
_need_decimal_fix = True
|
||||
|
||||
colspecs = {
|
||||
#sqltypes.Numeric : _MSNumeric,
|
||||
sqltypes.DateTime : _MSDateTime,
|
||||
sqltypes.Date : _MSDate,
|
||||
sqltypes.Time : TIME,
|
||||
sqltypes.Numeric: _MSNumeric_mxodbc,
|
||||
sqltypes.DateTime: _MSDateTime,
|
||||
sqltypes.Date: _MSDate_mxodbc,
|
||||
sqltypes.Time: _MSTime_mxodbc,
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, description_encoding='latin-1', **params):
|
||||
def __init__(self, description_encoding=None, **params):
|
||||
super(MSDialect_mxodbc, self).__init__(**params)
|
||||
self.description_encoding = description_encoding
|
||||
|
||||
dialect = MSDialect_mxodbc
|
||||
|
||||
|
||||
@@ -1,32 +1,18 @@
|
||||
# mssql/pymssql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for the pymssql dialect.
|
||||
|
||||
This dialect supports pymssql 1.0 and greater.
|
||||
|
||||
pymssql is available at:
|
||||
|
||||
http://pymssql.sourceforge.net/
|
||||
|
||||
Connecting
|
||||
^^^^^^^^^^
|
||||
|
||||
Sample connect string::
|
||||
|
||||
mssql+pymssql://<username>:<password>@<freetds_name>
|
||||
|
||||
Adding "?charset=utf8" or similar will cause pymssql to return
|
||||
strings as Python unicode objects. This can potentially improve
|
||||
performance in some scenarios as decoding of strings is
|
||||
handled natively.
|
||||
.. dialect:: mssql+pymssql
|
||||
:name: pymssql
|
||||
:dbapi: pymssql
|
||||
:connectstring: mssql+pymssql://<username>:<password>@<freetds_name>?charset=utf8
|
||||
:url: http://pymssql.sourceforge.net/
|
||||
|
||||
Limitations
|
||||
^^^^^^^^^^^
|
||||
-----------
|
||||
|
||||
pymssql inherits a lot of limitations from FreeTDS, including:
|
||||
|
||||
@@ -38,10 +24,11 @@ pymssql inherits a lot of limitations from FreeTDS, including:
|
||||
Please consult the pymssql documentation for further information.
|
||||
|
||||
"""
|
||||
from sqlalchemy.dialects.mssql.base import MSDialect
|
||||
from sqlalchemy import types as sqltypes, util, processors
|
||||
from .base import MSDialect
|
||||
from ... import types as sqltypes, util, processors
|
||||
import re
|
||||
|
||||
|
||||
class _MSNumeric_pymssql(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, type_):
|
||||
if not self.asdecimal:
|
||||
@@ -49,6 +36,7 @@ class _MSNumeric_pymssql(sqltypes.Numeric):
|
||||
else:
|
||||
return sqltypes.Numeric.result_processor(self, dialect, type_)
|
||||
|
||||
|
||||
class MSDialect_pymssql(MSDialect):
|
||||
supports_sane_rowcount = False
|
||||
driver = 'pymssql'
|
||||
@@ -56,16 +44,17 @@ class MSDialect_pymssql(MSDialect):
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric:_MSNumeric_pymssql,
|
||||
sqltypes.Float:sqltypes.Float,
|
||||
sqltypes.Numeric: _MSNumeric_pymssql,
|
||||
sqltypes.Float: sqltypes.Float,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
module = __import__('pymssql')
|
||||
# pymmsql doesn't have a Binary method. we use string
|
||||
# TODO: monkeypatching here is less than ideal
|
||||
module.Binary = str
|
||||
module.Binary = lambda x: x if hasattr(x, 'decode') else str(x)
|
||||
|
||||
client_ver = tuple(int(x) for x in module.__version__.split("."))
|
||||
if client_ver < (1, ):
|
||||
@@ -96,6 +85,9 @@ class MSDialect_pymssql(MSDialect):
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
for msg in (
|
||||
"Adaptive Server connection timed out",
|
||||
"Net-Lib error during Connection reset by peer",
|
||||
"message 20003", # connection timeout
|
||||
"Error 10054",
|
||||
"Not connected to any MS SQL server",
|
||||
"Connection is closed"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# mssql/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for MS-SQL via pyodbc.
|
||||
.. dialect:: mssql+pyodbc
|
||||
:name: PyODBC
|
||||
:dbapi: pyodbc
|
||||
:connectstring: mssql+pyodbc://<username>:<password>@<dsnname>
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
pyodbc is available at:
|
||||
|
||||
http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
Connecting
|
||||
^^^^^^^^^^
|
||||
Additional Connection Examples
|
||||
-------------------------------
|
||||
|
||||
Examples of pyodbc connection string URLs:
|
||||
|
||||
@@ -81,7 +81,7 @@ the python shell. For example::
|
||||
'dsn%3Dmydsn%3BDatabase%3Ddb'
|
||||
|
||||
Unicode Binds
|
||||
^^^^^^^^^^^^^
|
||||
-------------
|
||||
|
||||
The current state of PyODBC on a unix backend with FreeTDS and/or
|
||||
EasySoft is poor regarding unicode; different OS platforms and versions of UnixODBC
|
||||
@@ -111,23 +111,23 @@ for unix + PyODBC.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mssql.base import MSExecutionContext, MSDialect
|
||||
from sqlalchemy.connectors.pyodbc import PyODBCConnector
|
||||
from sqlalchemy import types as sqltypes, util
|
||||
from .base import MSExecutionContext, MSDialect
|
||||
from ...connectors.pyodbc import PyODBCConnector
|
||||
from ... import types as sqltypes, util
|
||||
import decimal
|
||||
|
||||
class _MSNumeric_pyodbc(sqltypes.Numeric):
|
||||
class _ms_numeric_pyodbc(object):
|
||||
|
||||
"""Turns Decimals with adjusted() < 0 or > 7 into strings.
|
||||
|
||||
This is the only method that is proven to work with Pyodbc+MSSQL
|
||||
without crashing (floats can be used but seem to cause sporadic
|
||||
crashes).
|
||||
The routines here are needed for older pyodbc versions
|
||||
as well as current mxODBC versions.
|
||||
|
||||
"""
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
|
||||
super_process = super(_MSNumeric_pyodbc, self).\
|
||||
super_process = super(_ms_numeric_pyodbc, self).\
|
||||
bind_processor(dialect)
|
||||
|
||||
if not dialect._need_decimal_fix:
|
||||
@@ -164,7 +164,7 @@ class _MSNumeric_pyodbc(sqltypes.Numeric):
|
||||
result = "%s%s%s" % (
|
||||
(value < 0 and '-' or ''),
|
||||
"".join([str(s) for s in _int]),
|
||||
"0" * (value.adjusted() - (len(_int)-1)))
|
||||
"0" * (value.adjusted() - (len(_int) - 1)))
|
||||
else:
|
||||
if (len(_int) - 1) > value.adjusted():
|
||||
result = "%s%s.%s" % (
|
||||
@@ -180,6 +180,11 @@ class _MSNumeric_pyodbc(sqltypes.Numeric):
|
||||
[str(s) for s in _int][0:value.adjusted() + 1]))
|
||||
return result
|
||||
|
||||
class _MSNumeric_pyodbc(_ms_numeric_pyodbc, sqltypes.Numeric):
|
||||
pass
|
||||
|
||||
class _MSFloat_pyodbc(_ms_numeric_pyodbc, sqltypes.Float):
|
||||
pass
|
||||
|
||||
class MSExecutionContext_pyodbc(MSExecutionContext):
|
||||
_embedded_scope_identity = False
|
||||
@@ -219,7 +224,7 @@ class MSExecutionContext_pyodbc(MSExecutionContext):
|
||||
# without closing it (FreeTDS particularly)
|
||||
row = self.cursor.fetchall()[0]
|
||||
break
|
||||
except self.dialect.dbapi.Error, e:
|
||||
except self.dialect.dbapi.Error as e:
|
||||
# no way around this - nextset() consumes the previous set
|
||||
# so we need to just keep flipping
|
||||
self.cursor.nextset()
|
||||
@@ -238,11 +243,12 @@ class MSDialect_pyodbc(PyODBCConnector, MSDialect):
|
||||
colspecs = util.update_copy(
|
||||
MSDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric:_MSNumeric_pyodbc
|
||||
sqltypes.Numeric: _MSNumeric_pyodbc,
|
||||
sqltypes.Float: _MSFloat_pyodbc
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, description_encoding='latin-1', **params):
|
||||
def __init__(self, description_encoding=None, **params):
|
||||
super(MSDialect_pyodbc, self).__init__(**params)
|
||||
self.description_encoding = description_encoding
|
||||
self.use_scope_identity = self.use_scope_identity and \
|
||||
|
||||
@@ -1,32 +1,22 @@
|
||||
# mssql/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the Microsoft SQL Server database via the zxjdbc JDBC
|
||||
connector.
|
||||
"""
|
||||
.. dialect:: mssql+zxjdbc
|
||||
:name: zxJDBC for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: mssql+zxjdbc://user:pass@host:port/dbname[?key=value&key=value...]
|
||||
:driverurl: http://jtds.sourceforge.net/
|
||||
|
||||
JDBC Driver
|
||||
-----------
|
||||
|
||||
Requires the jTDS driver, available from: http://jtds.sourceforge.net/
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
URLs are of the standard form of
|
||||
``mssql+zxjdbc://user:pass@host:port/dbname[?key=value&key=value...]``.
|
||||
|
||||
Additional arguments which may be specified either as query string
|
||||
arguments on the URL, or as keyword arguments to
|
||||
:func:`~sqlalchemy.create_engine()` will be passed as Connection
|
||||
properties to the underlying JDBC driver.
|
||||
|
||||
"""
|
||||
from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.mssql.base import MSDialect, MSExecutionContext
|
||||
from sqlalchemy.engine import base
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
from .base import MSDialect, MSExecutionContext
|
||||
from ... import engine
|
||||
|
||||
|
||||
class MSExecutionContext_zxjdbc(MSExecutionContext):
|
||||
|
||||
@@ -46,13 +36,13 @@ class MSExecutionContext_zxjdbc(MSExecutionContext):
|
||||
try:
|
||||
row = self.cursor.fetchall()[0]
|
||||
break
|
||||
except self.dialect.dbapi.Error, e:
|
||||
except self.dialect.dbapi.Error:
|
||||
self.cursor.nextset()
|
||||
self._lastrowid = int(row[0])
|
||||
|
||||
if (self.isinsert or self.isupdate or self.isdelete) and \
|
||||
self.compiled.returning:
|
||||
self._result_proxy = base.FullyBufferedResultProxy(self)
|
||||
self._result_proxy = engine.FullyBufferedResultProxy(self)
|
||||
|
||||
if self._enable_identity_insert:
|
||||
table = self.dialect.identifier_preparer.format_table(
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# mysql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.mysql import base, mysqldb, oursql, \
|
||||
from . import base, mysqldb, oursql, \
|
||||
pyodbc, zxjdbc, mysqlconnector, pymysql,\
|
||||
gaerdbms
|
||||
gaerdbms, cymysql
|
||||
|
||||
# default dialect
|
||||
base.dialect = mysqldb.dialect
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import \
|
||||
from .base import \
|
||||
BIGINT, BINARY, BIT, BLOB, BOOLEAN, CHAR, DATE, DATETIME, \
|
||||
DECIMAL, DOUBLE, ENUM, DECIMAL,\
|
||||
FLOAT, INTEGER, INTEGER, LONGBLOB, LONGTEXT, MEDIUMBLOB, \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
# mysql/cymysql.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
|
||||
.. dialect:: mysql+cymysql
|
||||
:name: CyMySQL
|
||||
:dbapi: cymysql
|
||||
:connectstring: mysql+cymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
:url: https://github.com/nakagami/CyMySQL
|
||||
|
||||
"""
|
||||
|
||||
from .mysqldb import MySQLDialect_mysqldb
|
||||
from .base import (BIT, MySQLDialect)
|
||||
from ... import util
|
||||
|
||||
class _cymysqlBIT(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
"""Convert a MySQL's 64 bit, variable length binary string to a long.
|
||||
"""
|
||||
|
||||
def process(value):
|
||||
if value is not None:
|
||||
v = 0
|
||||
for i in util.iterbytes(value):
|
||||
v = v << 8 | i
|
||||
return v
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class MySQLDialect_cymysql(MySQLDialect_mysqldb):
|
||||
driver = 'cymysql'
|
||||
|
||||
description_encoding = None
|
||||
supports_sane_rowcount = True
|
||||
supports_sane_multi_rowcount = False
|
||||
supports_unicode_statements = True
|
||||
|
||||
colspecs = util.update_copy(
|
||||
MySQLDialect.colspecs,
|
||||
{
|
||||
BIT: _cymysqlBIT,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('cymysql')
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
return exception.errno
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
return self._extract_error_code(e) in \
|
||||
(2006, 2013, 2014, 2045, 2055)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
# if underlying connection is closed,
|
||||
# this is the error you get
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
dialect = MySQLDialect_cymysql
|
||||
@@ -1,26 +1,20 @@
|
||||
# mysql/gaerdbms.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
"""Support for Google Cloud SQL on Google App Engine.
|
||||
"""
|
||||
.. dialect:: mysql+gaerdbms
|
||||
:name: Google Cloud SQL
|
||||
:dbapi: rdbms
|
||||
:connectstring: mysql+gaerdbms:///<dbname>?instance=<instancename>
|
||||
:url: https://developers.google.com/appengine/docs/python/cloud-sql/developers-guide
|
||||
|
||||
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
|
||||
changes.
|
||||
This dialect is based primarily on the :mod:`.mysql.mysqldb` dialect with minimal
|
||||
changes.
|
||||
|
||||
.. versionadded:: 0.7.8
|
||||
.. versionadded:: 0.7.8
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
mysql+gaerdbms:///<dbname>
|
||||
|
||||
E.g.::
|
||||
|
||||
create_engine('mysql+gaerdbms:///mydb',
|
||||
connect_args={"instance":"instancename"})
|
||||
|
||||
Pooling
|
||||
-------
|
||||
@@ -32,11 +26,17 @@ default.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
from sqlalchemy.pool import NullPool
|
||||
import os
|
||||
|
||||
from .mysqldb import MySQLDialect_mysqldb
|
||||
from ...pool import NullPool
|
||||
import re
|
||||
|
||||
|
||||
def _is_dev_environment():
|
||||
return os.environ.get('SERVER_SOFTWARE', '').startswith('Development/')
|
||||
|
||||
|
||||
class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
|
||||
@classmethod
|
||||
@@ -49,7 +49,10 @@ class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
# see also http://stackoverflow.com/q/14224679/34549
|
||||
from google.appengine.api import apiproxy_stub_map
|
||||
|
||||
if apiproxy_stub_map.apiproxy.GetStub('rdbms'):
|
||||
if _is_dev_environment():
|
||||
from google.appengine.api import rdbms_mysqldb
|
||||
return rdbms_mysqldb
|
||||
elif apiproxy_stub_map.apiproxy.GetStub('rdbms'):
|
||||
from google.storage.speckle.python.api import rdbms_apiproxy
|
||||
return rdbms_apiproxy
|
||||
else:
|
||||
@@ -63,21 +66,18 @@ class MySQLDialect_gaerdbms(MySQLDialect_mysqldb):
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args()
|
||||
# 'dsn' and 'instance' are because we are skipping
|
||||
# the traditional google.api.rdbms wrapper
|
||||
|
||||
opts['dsn'] = ''
|
||||
opts['instance'] = url.query['instance']
|
||||
if not _is_dev_environment():
|
||||
# 'dsn' and 'instance' are because we are skipping
|
||||
# the traditional google.api.rdbms wrapper
|
||||
opts['dsn'] = ''
|
||||
opts['instance'] = url.query['instance']
|
||||
return [], opts
|
||||
|
||||
def _extract_error_code(self, exception):
|
||||
match = re.compile(r"^(\d+):").match(str(exception))
|
||||
match = re.compile(r"^(\d+)L?:|^\((\d+)L?,").match(str(exception))
|
||||
# The rdbms api will wrap then re-raise some types of errors
|
||||
# making this regex return no matches.
|
||||
if match:
|
||||
code = match.group(1)
|
||||
else:
|
||||
code = None
|
||||
code = match.group(1) or match.group(2) if match else None
|
||||
if code:
|
||||
return int(code)
|
||||
|
||||
|
||||
@@ -1,34 +1,25 @@
|
||||
# mysql/mysqlconnector.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via the MySQL Connector/Python adapter.
|
||||
"""
|
||||
.. dialect:: mysql+mysqlconnector
|
||||
:name: MySQL Connector/Python
|
||||
:dbapi: myconnpy
|
||||
:connectstring: mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: https://launchpad.net/myconnpy
|
||||
|
||||
MySQL Connector/Python is available at:
|
||||
|
||||
https://launchpad.net/myconnpy
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import (MySQLDialect,
|
||||
from .base import (MySQLDialect,
|
||||
MySQLExecutionContext, MySQLCompiler, MySQLIdentifierPreparer,
|
||||
BIT)
|
||||
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
|
||||
from sqlalchemy import processors
|
||||
from ... import util
|
||||
|
||||
|
||||
class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
|
||||
|
||||
@@ -37,24 +28,28 @@ class MySQLExecutionContext_mysqlconnector(MySQLExecutionContext):
|
||||
|
||||
|
||||
class MySQLCompiler_mysqlconnector(MySQLCompiler):
|
||||
def visit_mod(self, binary, **kw):
|
||||
return self.process(binary.left) + " %% " + self.process(binary.right)
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
|
||||
|
||||
class MySQLIdentifierPreparer_mysqlconnector(MySQLIdentifierPreparer):
|
||||
|
||||
def _escape_identifier(self, value):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace("%", "%%")
|
||||
|
||||
|
||||
class _myconnpyBIT(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
"""MySQL-connector already converts mysql bits, so."""
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
driver = 'mysqlconnector'
|
||||
supports_unicode_statements = True
|
||||
@@ -84,12 +79,13 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
|
||||
opts.update(url.query)
|
||||
|
||||
util.coerce_kw_type(opts, 'buffered', bool)
|
||||
util.coerce_kw_type(opts, 'raise_on_warnings', bool)
|
||||
opts['buffered'] = True
|
||||
opts['raise_on_warnings'] = True
|
||||
opts.setdefault('buffered', True)
|
||||
opts.setdefault('raise_on_warnings', True)
|
||||
|
||||
# FOUND_ROWS must be set in ClientFlag to enable
|
||||
# supports_sane_rowcount.
|
||||
@@ -116,9 +112,10 @@ class MySQLDialect_mysqlconnector(MySQLDialect):
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
errnos = (2006, 2013, 2014, 2045, 2055, 2048)
|
||||
exceptions = (self.dbapi.OperationalError,self.dbapi.InterfaceError)
|
||||
exceptions = (self.dbapi.OperationalError, self.dbapi.InterfaceError)
|
||||
if isinstance(e, exceptions):
|
||||
return e.errno in errnos
|
||||
return e.errno in errnos or \
|
||||
"MySQL Connection not available." in str(e)
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
# mysql/mysqldb.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via the MySQL-python adapter.
|
||||
"""
|
||||
|
||||
MySQL-Python is available at:
|
||||
.. dialect:: mysql+mysqldb
|
||||
:name: MySQL-Python
|
||||
:dbapi: mysqldb
|
||||
:connectstring: mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: http://sourceforge.net/projects/mysql-python
|
||||
|
||||
http://sourceforge.net/projects/mysql-python
|
||||
|
||||
At least version 1.2.1 or 1.2.2 should be used.
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
|
||||
Unicode
|
||||
-------
|
||||
@@ -54,15 +48,16 @@ It is strongly advised to use the latest version of MySQL-Python.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import (MySQLDialect, MySQLExecutionContext,
|
||||
from .base import (MySQLDialect, MySQLExecutionContext,
|
||||
MySQLCompiler, MySQLIdentifierPreparer)
|
||||
from sqlalchemy.connectors.mysqldb import (
|
||||
from ...connectors.mysqldb import (
|
||||
MySQLDBExecutionContext,
|
||||
MySQLDBCompiler,
|
||||
MySQLDBIdentifierPreparer,
|
||||
MySQLDBConnector
|
||||
)
|
||||
|
||||
|
||||
class MySQLExecutionContext_mysqldb(MySQLDBExecutionContext, MySQLExecutionContext):
|
||||
pass
|
||||
|
||||
@@ -74,6 +69,7 @@ class MySQLCompiler_mysqldb(MySQLDBCompiler, MySQLCompiler):
|
||||
class MySQLIdentifierPreparer_mysqldb(MySQLDBIdentifierPreparer, MySQLIdentifierPreparer):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLDialect_mysqldb(MySQLDBConnector, MySQLDialect):
|
||||
execution_ctx_cls = MySQLExecutionContext_mysqldb
|
||||
statement_compiler = MySQLCompiler_mysqldb
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
# mysql/oursql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via the oursql adapter.
|
||||
"""
|
||||
|
||||
OurSQL is available at:
|
||||
|
||||
http://packages.python.org/oursql/
|
||||
|
||||
Connecting
|
||||
-----------
|
||||
|
||||
Connect string format::
|
||||
|
||||
mysql+oursql://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
.. dialect:: mysql+oursql
|
||||
:name: OurSQL
|
||||
:dbapi: oursql
|
||||
:connectstring: mysql+oursql://<user>:<password>@<host>[:<port>]/<dbname>
|
||||
:url: http://packages.python.org/oursql/
|
||||
|
||||
Unicode
|
||||
-------
|
||||
@@ -40,13 +35,8 @@ defaults to, there is a separate parameter::
|
||||
|
||||
import re
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import (BIT, MySQLDialect, MySQLExecutionContext,
|
||||
MySQLCompiler, MySQLIdentifierPreparer)
|
||||
from sqlalchemy.engine import base as engine_base, default
|
||||
from sqlalchemy.sql import operators as sql_operators
|
||||
from sqlalchemy import exc, log, schema, sql, types as sqltypes, util
|
||||
from sqlalchemy import processors
|
||||
|
||||
from .base import (BIT, MySQLDialect, MySQLExecutionContext)
|
||||
from ... import types as sqltypes, util
|
||||
|
||||
|
||||
class _oursqlBIT(BIT):
|
||||
@@ -62,12 +52,13 @@ class MySQLExecutionContext_oursql(MySQLExecutionContext):
|
||||
def plain_query(self):
|
||||
return self.execution_options.get('_oursql_plain_query', False)
|
||||
|
||||
|
||||
class MySQLDialect_oursql(MySQLDialect):
|
||||
driver = 'oursql'
|
||||
# Py2K
|
||||
supports_unicode_binds = True
|
||||
supports_unicode_statements = True
|
||||
# end Py2K
|
||||
|
||||
if util.py2k:
|
||||
supports_unicode_binds = True
|
||||
supports_unicode_statements = True
|
||||
|
||||
supports_native_decimal = True
|
||||
|
||||
@@ -99,12 +90,11 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
connection.cursor().execute('BEGIN', plain_query=True)
|
||||
|
||||
def _xa_query(self, connection, query, xid):
|
||||
# Py2K
|
||||
arg = connection.connection._escape_string(xid)
|
||||
# end Py2K
|
||||
# Py3K
|
||||
# charset = self._connection_charset
|
||||
# arg = connection.connection._escape_string(xid.encode(charset)).decode(charset)
|
||||
if util.py2k:
|
||||
arg = connection.connection._escape_string(xid)
|
||||
else:
|
||||
charset = self._connection_charset
|
||||
arg = connection.connection._escape_string(xid.encode(charset)).decode(charset)
|
||||
arg = "'%s'" % arg
|
||||
connection.execution_options(_oursql_plain_query=True).execute(query % arg)
|
||||
|
||||
@@ -135,64 +125,67 @@ class MySQLDialect_oursql(MySQLDialect):
|
||||
# Q: why didn't we need all these "plain_query" overrides earlier ?
|
||||
# am i on a newer/older version of OurSQL ?
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
return MySQLDialect.has_table(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table_name, schema)
|
||||
|
||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||
return MySQLDialect.get_table_options(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema = schema,
|
||||
**kw
|
||||
return MySQLDialect.has_table(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema
|
||||
)
|
||||
|
||||
def get_table_options(self, connection, table_name, schema=None, **kw):
|
||||
return MySQLDialect.get_table_options(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema=schema,
|
||||
**kw
|
||||
)
|
||||
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
return MySQLDialect.get_columns(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema=schema,
|
||||
**kw
|
||||
return MySQLDialect.get_columns(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
table_name,
|
||||
schema=schema,
|
||||
**kw
|
||||
)
|
||||
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
return MySQLDialect.get_view_names(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
schema=schema,
|
||||
**kw
|
||||
return MySQLDialect.get_view_names(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
schema=schema,
|
||||
**kw
|
||||
)
|
||||
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
return MySQLDialect.get_table_names(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
schema
|
||||
return MySQLDialect.get_table_names(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
schema
|
||||
)
|
||||
|
||||
def get_schema_names(self, connection, **kw):
|
||||
return MySQLDialect.get_schema_names(self,
|
||||
connection.connect().\
|
||||
execution_options(_oursql_plain_query=True),
|
||||
**kw
|
||||
return MySQLDialect.get_schema_names(
|
||||
self,
|
||||
connection.connect().execution_options(_oursql_plain_query=True),
|
||||
**kw
|
||||
)
|
||||
|
||||
def initialize(self, connection):
|
||||
return MySQLDialect.initialize(
|
||||
self,
|
||||
connection.execution_options(_oursql_plain_query=True)
|
||||
)
|
||||
self,
|
||||
connection.execution_options(_oursql_plain_query=True)
|
||||
)
|
||||
|
||||
def _show_create_table(self, connection, table, charset=None,
|
||||
full_name=None):
|
||||
return MySQLDialect._show_create_table(self,
|
||||
connection.contextual_connect(close_with_result=True).
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table, charset, full_name)
|
||||
return MySQLDialect._show_create_table(
|
||||
self,
|
||||
connection.contextual_connect(close_with_result=True).
|
||||
execution_options(_oursql_plain_query=True),
|
||||
table, charset, full_name
|
||||
)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.ProgrammingError):
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
# mysql/pymysql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via the pymysql adapter.
|
||||
"""
|
||||
|
||||
pymysql is available at:
|
||||
|
||||
http://code.google.com/p/pymysql/
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string::
|
||||
|
||||
mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
.. dialect:: mysql+pymysql
|
||||
:name: PyMySQL
|
||||
:dbapi: pymysql
|
||||
:connectstring: mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]
|
||||
:url: http://code.google.com/p/pymysql/
|
||||
|
||||
MySQL-Python Compatibility
|
||||
--------------------------
|
||||
@@ -26,14 +21,24 @@ the pymysql driver as well.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.mysqldb import MySQLDialect_mysqldb
|
||||
from .mysqldb import MySQLDialect_mysqldb
|
||||
from ...util import py3k
|
||||
|
||||
class MySQLDialect_pymysql(MySQLDialect_mysqldb):
|
||||
driver = 'pymysql'
|
||||
|
||||
description_encoding = None
|
||||
if py3k:
|
||||
supports_unicode_statements = True
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
return __import__('pymysql')
|
||||
|
||||
dialect = MySQLDialect_pymysql
|
||||
if py3k:
|
||||
def _extract_error_code(self, exception):
|
||||
if isinstance(exception.args[0], Exception):
|
||||
exception = exception.args[0]
|
||||
return exception.args[0]
|
||||
|
||||
dialect = MySQLDialect_pymysql
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
# mysql/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via the pyodbc adapter.
|
||||
"""
|
||||
|
||||
pyodbc is available at:
|
||||
|
||||
http://pypi.python.org/pypi/pyodbc/
|
||||
.. dialect:: mysql+pyodbc
|
||||
:name: PyODBC
|
||||
:dbapi: pyodbc
|
||||
:connectstring: mysql+pyodbc://<username>:<password>@<dsnname>
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string::
|
||||
|
||||
mysql+pyodbc://<username>:<password>@<dsnname>
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
@@ -27,12 +24,12 @@ of OurSQL, MySQLdb, or MySQL-connector/Python.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import MySQLDialect, MySQLExecutionContext
|
||||
from sqlalchemy.connectors.pyodbc import PyODBCConnector
|
||||
from sqlalchemy.engine import base as engine_base
|
||||
from sqlalchemy import util
|
||||
from .base import MySQLDialect, MySQLExecutionContext
|
||||
from ...connectors.pyodbc import PyODBCConnector
|
||||
from ... import util
|
||||
import re
|
||||
|
||||
|
||||
class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
|
||||
|
||||
def get_lastrowid(self):
|
||||
@@ -42,6 +39,7 @@ class MySQLExecutionContext_pyodbc(MySQLExecutionContext):
|
||||
cursor.close()
|
||||
return lastrowid
|
||||
|
||||
|
||||
class MySQLDialect_pyodbc(PyODBCConnector, MySQLDialect):
|
||||
supports_unicode_statements = False
|
||||
execution_ctx_cls = MySQLExecutionContext_pyodbc
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
# mysql/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the MySQL database via Jython's zxjdbc JDBC connector.
|
||||
"""
|
||||
|
||||
JDBC Driver
|
||||
-----------
|
||||
|
||||
The official MySQL JDBC driver is at
|
||||
http://dev.mysql.com/downloads/connector/j/.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
Connect string format:
|
||||
|
||||
mysql+zxjdbc://<user>:<password>@<hostname>[:<port>]/<database>
|
||||
.. dialect:: mysql+zxjdbc
|
||||
:name: zxjdbc for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: mysql+zxjdbc://<user>:<password>@<hostname>[:<port>]/<database>
|
||||
:driverurl: http://dev.mysql.com/downloads/connector/j/
|
||||
|
||||
Character Sets
|
||||
--------------
|
||||
@@ -31,9 +24,10 @@ overriden via a ``create_engine`` URL parameter.
|
||||
"""
|
||||
import re
|
||||
|
||||
from sqlalchemy import types as sqltypes, util
|
||||
from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.mysql.base import BIT, MySQLDialect, MySQLExecutionContext
|
||||
from ... import types as sqltypes, util
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
from .base import BIT, MySQLDialect, MySQLExecutionContext
|
||||
|
||||
|
||||
class _ZxJDBCBit(BIT):
|
||||
def result_processor(self, dialect, coltype):
|
||||
@@ -43,7 +37,7 @@ class _ZxJDBCBit(BIT):
|
||||
return value
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
v = 0L
|
||||
v = 0
|
||||
for i in value:
|
||||
v = v << 8 | (i & 0xff)
|
||||
value = v
|
||||
@@ -103,7 +97,7 @@ class MySQLDialect_zxjdbc(ZxJDBCConnector, MySQLDialect):
|
||||
if c:
|
||||
return int(c)
|
||||
|
||||
def _get_server_version_info(self,connection):
|
||||
def _get_server_version_info(self, connection):
|
||||
dbapi_con = connection.connection
|
||||
version = []
|
||||
r = re.compile('[.\-]')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# oracle/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,7 +12,7 @@ from sqlalchemy.dialects.oracle.base import \
|
||||
VARCHAR, NVARCHAR, CHAR, DATE, DATETIME, NUMBER,\
|
||||
BLOB, BFILE, CLOB, NCLOB, TIMESTAMP, RAW,\
|
||||
FLOAT, DOUBLE_PRECISION, LONG, dialect, INTERVAL,\
|
||||
VARCHAR2, NVARCHAR2, ROWID
|
||||
VARCHAR2, NVARCHAR2, ROWID, dialect
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
# oracle/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the Oracle database.
|
||||
"""
|
||||
.. dialect:: oracle
|
||||
:name: Oracle
|
||||
|
||||
Oracle version 8 through current (11g at the time of this writing) are supported.
|
||||
|
||||
For information on connecting via specific drivers, see the documentation
|
||||
for that driver.
|
||||
Oracle version 8 through current (11g at the time of this writing) are supported.
|
||||
|
||||
Connect Arguments
|
||||
-----------------
|
||||
@@ -17,12 +16,12 @@ Connect Arguments
|
||||
The dialect supports several :func:`~sqlalchemy.create_engine()` arguments which
|
||||
affect the behavior of the dialect regardless of driver in use.
|
||||
|
||||
* *use_ansi* - Use ANSI JOIN constructs (see the section on Oracle 8). Defaults
|
||||
* ``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.
|
||||
|
||||
* *use_binds_for_limits* - defaults to ``True``. see the section on LIMIT/OFFSET.
|
||||
* ``use_binds_for_limits`` - defaults to ``True``. see the section on LIMIT/OFFSET.
|
||||
|
||||
Auto Increment Behavior
|
||||
-----------------------
|
||||
@@ -100,6 +99,41 @@ http://www.sqlalchemy.org/trac/wiki/UsageRecipes/WindowFunctionsByDefault
|
||||
which installs a select compiler that overrides the generation of limit/offset with
|
||||
a window function.
|
||||
|
||||
RETURNING Support
|
||||
-----------------
|
||||
|
||||
The Oracle database supports a limited form of RETURNING, in order to retrieve result
|
||||
sets of matched rows from INSERT, UPDATE and DELETE statements. Oracle's
|
||||
RETURNING..INTO syntax only supports one row being returned, as it relies upon
|
||||
OUT parameters in order to function. In addition, supported DBAPIs have further
|
||||
limitations (see :ref:`cx_oracle_returning`).
|
||||
|
||||
SQLAlchemy's "implicit returning" feature, which employs RETURNING within an INSERT
|
||||
and sometimes an UPDATE statement in order to fetch newly generated primary key values
|
||||
and other SQL defaults and expressions, is normally enabled on the Oracle
|
||||
backend. By default, "implicit returning" typically only fetches the value of a
|
||||
single ``nextval(some_seq)`` expression embedded into an INSERT in order to increment
|
||||
a sequence within an INSERT statement and get the value back at the same time.
|
||||
To disable this feature across the board, specify ``implicit_returning=False`` to
|
||||
:func:`.create_engine`::
|
||||
|
||||
engine = create_engine("oracle://scott:tiger@dsn", implicit_returning=False)
|
||||
|
||||
Implicit returning can also be disabled on a table-by-table basis as a table option::
|
||||
|
||||
# Core Table
|
||||
my_table = Table("my_table", metadata, ..., implicit_returning=False)
|
||||
|
||||
|
||||
# declarative
|
||||
class MyClass(Base):
|
||||
__tablename__ = 'my_table'
|
||||
__table_args__ = {"implicit_returning": False}
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`cx_oracle_returning` - additional cx_oracle-specific restrictions on implicit returning.
|
||||
|
||||
ON UPDATE CASCADE
|
||||
-----------------
|
||||
|
||||
@@ -134,16 +168,16 @@ 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
|
||||
oracle_resolve_synonyms=True as a keyword argument to the Table construct. If DBLINK
|
||||
is not in use this flag should be left off.
|
||||
indicated by synonyms, either in local or remote schemas or accessed over DBLINK,
|
||||
by passing the flag oracle_resolve_synonyms=True as a
|
||||
keyword argument to the Table construct. If synonyms are not in use
|
||||
this flag should be left off.
|
||||
|
||||
"""
|
||||
|
||||
import random, re
|
||||
import re
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import util, sql, log
|
||||
from sqlalchemy import util, sql
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
from sqlalchemy.sql import compiler, visitors, expression
|
||||
from sqlalchemy.sql import operators as sql_operators, functions as sql_functions
|
||||
@@ -165,18 +199,22 @@ RESERVED_WORDS = \
|
||||
NO_ARG_FNS = set('UID CURRENT_DATE SYSDATE USER '
|
||||
'CURRENT_TIME CURRENT_TIMESTAMP'.split())
|
||||
|
||||
|
||||
class RAW(sqltypes._Binary):
|
||||
__visit_name__ = 'RAW'
|
||||
OracleRaw = RAW
|
||||
|
||||
|
||||
class NCLOB(sqltypes.Text):
|
||||
__visit_name__ = 'NCLOB'
|
||||
|
||||
|
||||
class VARCHAR2(VARCHAR):
|
||||
__visit_name__ = 'VARCHAR2'
|
||||
|
||||
NVARCHAR2 = NVARCHAR
|
||||
|
||||
|
||||
class NUMBER(sqltypes.Numeric, sqltypes.Integer):
|
||||
__visit_name__ = 'NUMBER'
|
||||
|
||||
@@ -202,18 +240,22 @@ class NUMBER(sqltypes.Numeric, sqltypes.Integer):
|
||||
|
||||
class DOUBLE_PRECISION(sqltypes.Numeric):
|
||||
__visit_name__ = 'DOUBLE_PRECISION'
|
||||
|
||||
def __init__(self, precision=None, scale=None, asdecimal=None):
|
||||
if asdecimal is None:
|
||||
asdecimal = False
|
||||
|
||||
super(DOUBLE_PRECISION, self).__init__(precision=precision, scale=scale, asdecimal=asdecimal)
|
||||
|
||||
|
||||
class BFILE(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'BFILE'
|
||||
|
||||
|
||||
class LONG(sqltypes.Text):
|
||||
__visit_name__ = 'LONG'
|
||||
|
||||
|
||||
class INTERVAL(sqltypes.TypeEngine):
|
||||
__visit_name__ = 'INTERVAL'
|
||||
|
||||
@@ -244,6 +286,7 @@ class INTERVAL(sqltypes.TypeEngine):
|
||||
def _type_affinity(self):
|
||||
return sqltypes.Interval
|
||||
|
||||
|
||||
class ROWID(sqltypes.TypeEngine):
|
||||
"""Oracle ROWID type.
|
||||
|
||||
@@ -253,33 +296,32 @@ class ROWID(sqltypes.TypeEngine):
|
||||
__visit_name__ = 'ROWID'
|
||||
|
||||
|
||||
|
||||
class _OracleBoolean(sqltypes.Boolean):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.NUMBER
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Boolean : _OracleBoolean,
|
||||
sqltypes.Interval : INTERVAL,
|
||||
sqltypes.Boolean: _OracleBoolean,
|
||||
sqltypes.Interval: INTERVAL,
|
||||
}
|
||||
|
||||
ischema_names = {
|
||||
'VARCHAR2' : VARCHAR,
|
||||
'NVARCHAR2' : NVARCHAR,
|
||||
'CHAR' : CHAR,
|
||||
'DATE' : DATE,
|
||||
'NUMBER' : NUMBER,
|
||||
'BLOB' : BLOB,
|
||||
'BFILE' : BFILE,
|
||||
'CLOB' : CLOB,
|
||||
'NCLOB' : NCLOB,
|
||||
'TIMESTAMP' : TIMESTAMP,
|
||||
'TIMESTAMP WITH TIME ZONE' : TIMESTAMP,
|
||||
'INTERVAL DAY TO SECOND' : INTERVAL,
|
||||
'RAW' : RAW,
|
||||
'FLOAT' : FLOAT,
|
||||
'DOUBLE PRECISION' : DOUBLE_PRECISION,
|
||||
'LONG' : LONG,
|
||||
'VARCHAR2': VARCHAR,
|
||||
'NVARCHAR2': NVARCHAR,
|
||||
'CHAR': CHAR,
|
||||
'DATE': DATE,
|
||||
'NUMBER': NUMBER,
|
||||
'BLOB': BLOB,
|
||||
'BFILE': BFILE,
|
||||
'CLOB': CLOB,
|
||||
'NCLOB': NCLOB,
|
||||
'TIMESTAMP': TIMESTAMP,
|
||||
'TIMESTAMP WITH TIME ZONE': TIMESTAMP,
|
||||
'INTERVAL DAY TO SECOND': INTERVAL,
|
||||
'RAW': RAW,
|
||||
'FLOAT': FLOAT,
|
||||
'DOUBLE PRECISION': DOUBLE_PRECISION,
|
||||
'LONG': LONG,
|
||||
}
|
||||
|
||||
|
||||
@@ -336,9 +378,11 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
if precision is None:
|
||||
return name
|
||||
elif scale is None:
|
||||
return "%(name)s(%(precision)s)" % {'name':name,'precision': precision}
|
||||
n = "%(name)s(%(precision)s)"
|
||||
return n % {'name': name, 'precision': precision}
|
||||
else:
|
||||
return "%(name)s(%(precision)s, %(scale)s)" % {'name':name,'precision': precision, 'scale' : scale}
|
||||
n = "%(name)s(%(precision)s, %(scale)s)"
|
||||
return n % {'name': name, 'precision': precision, 'scale': scale}
|
||||
|
||||
def visit_string(self, type_):
|
||||
return self.visit_VARCHAR2(type_)
|
||||
@@ -354,13 +398,14 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return self._visit_varchar(type_, '', '')
|
||||
|
||||
def _visit_varchar(self, type_, n, num):
|
||||
if not n and self.dialect._supports_char_length:
|
||||
return "VARCHAR%(two)s(%(length)s CHAR)" % {
|
||||
'length' : type_.length,
|
||||
'two':num}
|
||||
if not type_.length:
|
||||
return "%(n)sVARCHAR%(two)s" % {'two': num, 'n': n}
|
||||
elif not n and self.dialect._supports_char_length:
|
||||
varchar = "VARCHAR%(two)s(%(length)s CHAR)"
|
||||
return varchar % {'length': type_.length, 'two': num}
|
||||
else:
|
||||
return "%(n)sVARCHAR%(two)s(%(length)s)" % {'length' : type_.length,
|
||||
'two':num, 'n':n}
|
||||
varchar = "%(n)sVARCHAR%(two)s(%(length)s)"
|
||||
return varchar % {'length': type_.length, 'two': num, 'n': n}
|
||||
|
||||
def visit_text(self, type_):
|
||||
return self.visit_CLOB(type_)
|
||||
@@ -382,13 +427,14 @@ class OracleTypeCompiler(compiler.GenericTypeCompiler):
|
||||
|
||||
def visit_RAW(self, type_):
|
||||
if type_.length:
|
||||
return "RAW(%(length)s)" % {'length' : type_.length}
|
||||
return "RAW(%(length)s)" % {'length': type_.length}
|
||||
else:
|
||||
return "RAW"
|
||||
|
||||
def visit_ROWID(self, type_):
|
||||
return "ROWID"
|
||||
|
||||
|
||||
class OracleCompiler(compiler.SQLCompiler):
|
||||
"""Oracle compiler modifies the lexical structure of Select
|
||||
statements to work under non-ANSI configured Oracle databases, if
|
||||
@@ -398,7 +444,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
compound_keywords = util.update_copy(
|
||||
compiler.SQLCompiler.compound_keywords,
|
||||
{
|
||||
expression.CompoundSelect.EXCEPT : 'MINUS'
|
||||
expression.CompoundSelect.EXCEPT: 'MINUS'
|
||||
}
|
||||
)
|
||||
|
||||
@@ -407,8 +453,9 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
self._quoted_bind_names = {}
|
||||
super(OracleCompiler, self).__init__(*args, **kwargs)
|
||||
|
||||
def visit_mod(self, binary, **kw):
|
||||
return "mod(%s, %s)" % (self.process(binary.left), self.process(binary.right))
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return "mod(%s, %s)" % (self.process(binary.left, **kw),
|
||||
self.process(binary.right, **kw))
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "CURRENT_TIMESTAMP"
|
||||
@@ -416,8 +463,15 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
def visit_char_length_func(self, fn, **kw):
|
||||
return "LENGTH" + self.function_argspec(fn, **kw)
|
||||
|
||||
def visit_match_op(self, binary, **kw):
|
||||
return "CONTAINS (%s, %s)" % (self.process(binary.left), self.process(binary.right))
|
||||
def visit_match_op_binary(self, binary, operator, **kw):
|
||||
return "CONTAINS (%s, %s)" % (self.process(binary.left),
|
||||
self.process(binary.right))
|
||||
|
||||
def visit_true(self, expr, **kw):
|
||||
return '1'
|
||||
|
||||
def visit_false(self, expr, **kw):
|
||||
return '0'
|
||||
|
||||
def get_select_hint_text(self, byfroms):
|
||||
return " ".join(
|
||||
@@ -444,8 +498,13 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
return compiler.SQLCompiler.visit_join(self, join, **kwargs)
|
||||
else:
|
||||
kwargs['asfrom'] = True
|
||||
if isinstance(join.right, expression.FromGrouping):
|
||||
right = join.right.element
|
||||
else:
|
||||
right = join.right
|
||||
return self.process(join.left, **kwargs) + \
|
||||
", " + self.process(join.right, **kwargs)
|
||||
", " + self.process(right, **kwargs)
|
||||
|
||||
|
||||
def _get_nonansi_join_whereclause(self, froms):
|
||||
clauses = []
|
||||
@@ -454,18 +513,20 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
if join.isouter:
|
||||
def visit_binary(binary):
|
||||
if binary.operator == sql_operators.eq:
|
||||
if binary.left.table is join.right:
|
||||
if join.right.is_derived_from(binary.left.table):
|
||||
binary.left = _OuterJoinColumn(binary.left)
|
||||
elif binary.right.table is join.right:
|
||||
elif join.right.is_derived_from(binary.right.table):
|
||||
binary.right = _OuterJoinColumn(binary.right)
|
||||
clauses.append(visitors.cloned_traverse(join.onclause, {},
|
||||
{'binary':visit_binary}))
|
||||
{'binary': visit_binary}))
|
||||
else:
|
||||
clauses.append(join.onclause)
|
||||
|
||||
for j in join.left, join.right:
|
||||
if isinstance(j, expression.Join):
|
||||
visit_join(j)
|
||||
elif isinstance(j, expression.FromGrouping):
|
||||
visit_join(j.element)
|
||||
|
||||
for f in froms:
|
||||
if isinstance(f, expression.Join):
|
||||
@@ -498,20 +559,25 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
return self.process(alias.original, **kwargs)
|
||||
|
||||
def returning_clause(self, stmt, returning_cols):
|
||||
columns = []
|
||||
binds = []
|
||||
for i, column in enumerate(expression._select_iterables(returning_cols)):
|
||||
if column.type._has_column_expression:
|
||||
col_expr = column.type.column_expression(column)
|
||||
else:
|
||||
col_expr = column
|
||||
outparam = sql.outparam("ret_%d" % i, type_=column.type)
|
||||
self.binds[outparam.key] = outparam
|
||||
binds.append(self.bindparam_string(self._truncate_bindparam(outparam)))
|
||||
columns.append(self.process(col_expr, within_columns_clause=False))
|
||||
self.result_map[outparam.key] = (
|
||||
outparam.key,
|
||||
(column, getattr(column, 'name', None),
|
||||
getattr(column, 'key', None)),
|
||||
column.type
|
||||
)
|
||||
|
||||
def create_out_param(col, i):
|
||||
bindparam = sql.outparam("ret_%d" % i, type_=col.type)
|
||||
self.binds[bindparam.key] = bindparam
|
||||
return self.bindparam_string(self._truncate_bindparam(bindparam))
|
||||
|
||||
columnlist = list(expression._select_iterables(returning_cols))
|
||||
|
||||
# within_columns_clause =False so that labels (foo AS bar) don't render
|
||||
columns = [self.process(c, within_columns_clause=False, result_map=self.result_map) for c in columnlist]
|
||||
|
||||
binds = [create_out_param(c, i) for i, c in enumerate(columnlist)]
|
||||
|
||||
return 'RETURNING ' + ', '.join(columns) + " INTO " + ", ".join(binds)
|
||||
return 'RETURNING ' + ', '.join(columns) + " INTO " + ", ".join(binds)
|
||||
|
||||
def _TODO_visit_compound_select(self, select):
|
||||
"""Need to determine how to get ``LIMIT``/``OFFSET`` into a ``UNION`` for Oracle."""
|
||||
@@ -524,12 +590,8 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
|
||||
if not getattr(select, '_oracle_visit', None):
|
||||
if not self.dialect.use_ansi:
|
||||
if self.stack and 'from' in self.stack[-1]:
|
||||
existingfroms = self.stack[-1]['from']
|
||||
else:
|
||||
existingfroms = None
|
||||
|
||||
froms = select._get_display_froms(existingfroms)
|
||||
froms = self._display_froms_for_select(
|
||||
select, kwargs.get('asfrom', False))
|
||||
whereclause = self._get_nonansi_join_whereclause(froms)
|
||||
if whereclause is not None:
|
||||
select = select.where(whereclause)
|
||||
@@ -570,7 +632,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
|
||||
# If needed, add the ora_rn, and wrap again with offset.
|
||||
if select._offset is None:
|
||||
limitselect.for_update = select.for_update
|
||||
limitselect._for_update_arg = select._for_update_arg
|
||||
select = limitselect
|
||||
else:
|
||||
limitselect = limitselect.column(
|
||||
@@ -579,7 +641,7 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
limitselect._is_wrapper = True
|
||||
|
||||
offsetselect = sql.select(
|
||||
[c for c in limitselect.c if c.key!='ora_rn'])
|
||||
[c for c in limitselect.c if c.key != 'ora_rn'])
|
||||
offsetselect._oracle_visit = True
|
||||
offsetselect._is_wrapper = True
|
||||
|
||||
@@ -587,9 +649,9 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
if not self.dialect.use_binds_for_limits:
|
||||
offset_value = sql.literal_column("%d" % offset_value)
|
||||
offsetselect.append_whereclause(
|
||||
sql.literal_column("ora_rn")>offset_value)
|
||||
sql.literal_column("ora_rn") > offset_value)
|
||||
|
||||
offsetselect.for_update = select.for_update
|
||||
offsetselect._for_update_arg = select._for_update_arg
|
||||
select = offsetselect
|
||||
|
||||
kwargs['iswrapper'] = getattr(select, '_is_wrapper', False)
|
||||
@@ -601,10 +663,20 @@ class OracleCompiler(compiler.SQLCompiler):
|
||||
def for_update_clause(self, select):
|
||||
if self.is_subquery():
|
||||
return ""
|
||||
elif select.for_update == "nowait":
|
||||
return " FOR UPDATE NOWAIT"
|
||||
else:
|
||||
return super(OracleCompiler, self).for_update_clause(select)
|
||||
|
||||
tmp = ' FOR UPDATE'
|
||||
|
||||
if select._for_update_arg.of:
|
||||
tmp += ' OF ' + ', '.join(
|
||||
self.process(elem) for elem in
|
||||
select._for_update_arg.of
|
||||
)
|
||||
|
||||
if select._for_update_arg.nowait:
|
||||
tmp += " NOWAIT"
|
||||
|
||||
return tmp
|
||||
|
||||
|
||||
class OracleDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
@@ -623,17 +695,22 @@ class OracleDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
return text
|
||||
|
||||
def visit_create_index(self, create, **kw):
|
||||
return super(OracleDDLCompiler, self).\
|
||||
visit_create_index(create, include_schema=True)
|
||||
|
||||
|
||||
class OracleIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
|
||||
reserved_words = set([x.lower() for x in RESERVED_WORDS])
|
||||
illegal_initial_characters = set(xrange(0, 10)).union(["_", "$"])
|
||||
illegal_initial_characters = set(range(0, 10)).union(["_", "$"])
|
||||
|
||||
def _bindparam_requires_quotes(self, value):
|
||||
"""Return True if the given identifier requires quoting."""
|
||||
lc_value = value.lower()
|
||||
return (lc_value in self.reserved_words
|
||||
or value[0] in self.illegal_initial_characters
|
||||
or not self.legal_characters.match(unicode(value))
|
||||
or not self.legal_characters.match(util.text_type(value))
|
||||
)
|
||||
|
||||
def format_savepoint(self, savepoint):
|
||||
@@ -647,6 +724,7 @@ class OracleExecutionContext(default.DefaultExecutionContext):
|
||||
self.dialect.identifier_preparer.format_sequence(seq) +
|
||||
".nextval FROM DUAL", type_)
|
||||
|
||||
|
||||
class OracleDialect(default.DefaultDialect):
|
||||
name = 'oracle'
|
||||
supports_alter = True
|
||||
@@ -736,10 +814,9 @@ class OracleDialect(default.DefaultDialect):
|
||||
def normalize_name(self, name):
|
||||
if name is None:
|
||||
return None
|
||||
# Py2K
|
||||
if isinstance(name, str):
|
||||
name = name.decode(self.encoding)
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
if isinstance(name, str):
|
||||
name = name.decode(self.encoding)
|
||||
if name.upper() == name and \
|
||||
not self.identifier_preparer._requires_quotes(name.lower()):
|
||||
return name.lower()
|
||||
@@ -751,16 +828,15 @@ class OracleDialect(default.DefaultDialect):
|
||||
return None
|
||||
elif name.lower() == name and not self.identifier_preparer._requires_quotes(name.lower()):
|
||||
name = name.upper()
|
||||
# Py2K
|
||||
if not self.supports_unicode_binds:
|
||||
name = name.encode(self.encoding)
|
||||
else:
|
||||
name = unicode(name)
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
if not self.supports_unicode_binds:
|
||||
name = name.encode(self.encoding)
|
||||
else:
|
||||
name = unicode(name)
|
||||
return name
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
return self.normalize_name(connection.execute(u'SELECT USER FROM DUAL').scalar())
|
||||
return self.normalize_name(connection.execute('SELECT USER FROM DUAL').scalar())
|
||||
|
||||
def _resolve_synonym(self, connection, desired_owner=None, desired_synonym=None, desired_table=None):
|
||||
"""search for a local synonym matching the given desired owner/name.
|
||||
@@ -770,14 +846,15 @@ class OracleDialect(default.DefaultDialect):
|
||||
returns the actual name, owner, dblink name, and synonym name if found.
|
||||
"""
|
||||
|
||||
q = "SELECT owner, table_owner, table_name, db_link, synonym_name FROM all_synonyms WHERE "
|
||||
q = "SELECT owner, table_owner, table_name, db_link, "\
|
||||
"synonym_name FROM all_synonyms WHERE "
|
||||
clauses = []
|
||||
params = {}
|
||||
if desired_synonym:
|
||||
clauses.append("synonym_name = :synonym_name")
|
||||
params['synonym_name'] = desired_synonym
|
||||
if desired_owner:
|
||||
clauses.append("table_owner = :desired_owner")
|
||||
clauses.append("owner = :desired_owner")
|
||||
params['desired_owner'] = desired_owner
|
||||
if desired_table:
|
||||
clauses.append("table_name = :tname")
|
||||
@@ -808,19 +885,29 @@ class OracleDialect(default.DefaultDialect):
|
||||
|
||||
if resolve_synonyms:
|
||||
actual_name, owner, dblink, synonym = self._resolve_synonym(
|
||||
connection,
|
||||
desired_owner=self.denormalize_name(schema),
|
||||
desired_synonym=self.denormalize_name(table_name)
|
||||
)
|
||||
connection,
|
||||
desired_owner=self.denormalize_name(schema),
|
||||
desired_synonym=self.denormalize_name(table_name)
|
||||
)
|
||||
else:
|
||||
actual_name, owner, dblink, synonym = None, None, None, None
|
||||
if not actual_name:
|
||||
actual_name = self.denormalize_name(table_name)
|
||||
if not dblink:
|
||||
dblink = ''
|
||||
if not owner:
|
||||
|
||||
if dblink:
|
||||
# using user_db_links here since all_db_links appears
|
||||
# to have more restricted permissions.
|
||||
# http://docs.oracle.com/cd/B28359_01/server.111/b28310/ds_admin005.htm
|
||||
# will need to hear from more users if we are doing
|
||||
# the right thing here. See [ticket:2619]
|
||||
owner = connection.scalar(
|
||||
sql.text("SELECT username FROM user_db_links "
|
||||
"WHERE db_link=:link"), link=dblink)
|
||||
dblink = "@" + dblink
|
||||
elif not owner:
|
||||
owner = self.denormalize_name(schema or self.default_schema_name)
|
||||
return (actual_name, owner, dblink, synonym)
|
||||
|
||||
return (actual_name, owner, dblink or '', synonym)
|
||||
|
||||
@reflection.cache
|
||||
def get_schema_names(self, connection, **kw):
|
||||
@@ -843,7 +930,6 @@ class OracleDialect(default.DefaultDialect):
|
||||
cursor = connection.execute(s, owner=schema)
|
||||
return [self.normalize_name(row[0]) for row in cursor]
|
||||
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
schema = self.denormalize_name(schema or self.default_schema_name)
|
||||
@@ -877,18 +963,24 @@ class OracleDialect(default.DefaultDialect):
|
||||
else:
|
||||
char_length_col = 'data_length'
|
||||
|
||||
c = connection.execute(sql.text(
|
||||
"SELECT column_name, data_type, %(char_length_col)s, data_precision, data_scale, "
|
||||
"nullable, data_default FROM ALL_TAB_COLUMNS%(dblink)s "
|
||||
"WHERE table_name = :table_name AND owner = :owner "
|
||||
"ORDER BY column_id" % {'dblink': dblink, 'char_length_col':char_length_col}),
|
||||
table_name=table_name, owner=schema)
|
||||
params = {"table_name": table_name}
|
||||
text = "SELECT column_name, data_type, %(char_length_col)s, "\
|
||||
"data_precision, data_scale, "\
|
||||
"nullable, data_default FROM ALL_TAB_COLUMNS%(dblink)s "\
|
||||
"WHERE table_name = :table_name"
|
||||
if schema is not None:
|
||||
params['owner'] = schema
|
||||
text += " AND owner = :owner "
|
||||
text += " ORDER BY column_id"
|
||||
text = text % {'dblink': dblink, 'char_length_col': char_length_col}
|
||||
|
||||
c = connection.execute(sql.text(text), **params)
|
||||
|
||||
for row in c:
|
||||
(colname, orig_colname, coltype, length, precision, scale, nullable, default) = \
|
||||
(self.normalize_name(row[0]), row[0], row[1], row[2], row[3], row[4], row[5]=='Y', row[6])
|
||||
(self.normalize_name(row[0]), row[0], row[1], row[2], row[3], row[4], row[5] == 'Y', row[6])
|
||||
|
||||
if coltype == 'NUMBER' :
|
||||
if coltype == 'NUMBER':
|
||||
coltype = NUMBER(precision, scale)
|
||||
elif coltype in ('VARCHAR2', 'NVARCHAR2', 'CHAR'):
|
||||
coltype = self.ischema_names.get(coltype)(length)
|
||||
@@ -908,7 +1000,7 @@ class OracleDialect(default.DefaultDialect):
|
||||
'type': coltype,
|
||||
'nullable': nullable,
|
||||
'default': default,
|
||||
'autoincrement':default is None
|
||||
'autoincrement': default is None
|
||||
}
|
||||
if orig_colname.lower() == orig_colname:
|
||||
cdict['quote'] = True
|
||||
@@ -920,33 +1012,40 @@ class OracleDialect(default.DefaultDialect):
|
||||
def get_indexes(self, connection, table_name, schema=None,
|
||||
resolve_synonyms=False, dblink='', **kw):
|
||||
|
||||
|
||||
info_cache = kw.get('info_cache')
|
||||
(table_name, schema, dblink, synonym) = \
|
||||
self._prepare_reflection_args(connection, table_name, schema,
|
||||
resolve_synonyms, dblink,
|
||||
info_cache=info_cache)
|
||||
indexes = []
|
||||
q = sql.text("""
|
||||
SELECT a.index_name, a.column_name, b.uniqueness
|
||||
FROM ALL_IND_COLUMNS%(dblink)s a,
|
||||
ALL_INDEXES%(dblink)s b
|
||||
WHERE
|
||||
a.index_name = b.index_name
|
||||
AND a.table_owner = b.table_owner
|
||||
AND a.table_name = b.table_name
|
||||
|
||||
AND a.table_name = :table_name
|
||||
AND a.table_owner = :schema
|
||||
ORDER BY a.index_name, a.column_position""" % {'dblink': dblink})
|
||||
rp = connection.execute(q, table_name=self.denormalize_name(table_name),
|
||||
schema=self.denormalize_name(schema))
|
||||
params = {'table_name': table_name}
|
||||
text = \
|
||||
"SELECT a.index_name, a.column_name, b.uniqueness "\
|
||||
"\nFROM ALL_IND_COLUMNS%(dblink)s a, "\
|
||||
"\nALL_INDEXES%(dblink)s b "\
|
||||
"\nWHERE "\
|
||||
"\na.index_name = b.index_name "\
|
||||
"\nAND a.table_owner = b.table_owner "\
|
||||
"\nAND a.table_name = b.table_name "\
|
||||
"\nAND a.table_name = :table_name "
|
||||
|
||||
if schema is not None:
|
||||
params['schema'] = schema
|
||||
text += "AND a.table_owner = :schema "
|
||||
|
||||
text += "ORDER BY a.index_name, a.column_position"
|
||||
|
||||
text = text % {'dblink': dblink}
|
||||
|
||||
q = sql.text(text)
|
||||
rp = connection.execute(q, **params)
|
||||
indexes = []
|
||||
last_index_name = None
|
||||
pkeys = self.get_primary_keys(connection, table_name, schema,
|
||||
resolve_synonyms=resolve_synonyms,
|
||||
dblink=dblink,
|
||||
info_cache=kw.get('info_cache'))
|
||||
pk_constraint = self.get_pk_constraint(
|
||||
connection, table_name, schema, resolve_synonyms=resolve_synonyms,
|
||||
dblink=dblink, info_cache=kw.get('info_cache'))
|
||||
pkeys = pk_constraint['constrained_columns']
|
||||
uniqueness = dict(NONUNIQUE=False, UNIQUE=True)
|
||||
|
||||
oracle_sys_col = re.compile(r'SYS_NC\d+\$', re.IGNORECASE)
|
||||
@@ -982,46 +1081,43 @@ class OracleDialect(default.DefaultDialect):
|
||||
def _get_constraint_data(self, connection, table_name, schema=None,
|
||||
dblink='', **kw):
|
||||
|
||||
rp = connection.execute(
|
||||
sql.text("""SELECT
|
||||
ac.constraint_name,
|
||||
ac.constraint_type,
|
||||
loc.column_name AS local_column,
|
||||
rem.table_name AS remote_table,
|
||||
rem.column_name AS remote_column,
|
||||
rem.owner AS remote_owner,
|
||||
loc.position as loc_pos,
|
||||
rem.position as rem_pos
|
||||
FROM all_constraints%(dblink)s ac,
|
||||
all_cons_columns%(dblink)s loc,
|
||||
all_cons_columns%(dblink)s rem
|
||||
WHERE ac.table_name = :table_name
|
||||
AND ac.constraint_type IN ('R','P')
|
||||
AND ac.owner = :owner
|
||||
AND ac.owner = loc.owner
|
||||
AND ac.constraint_name = loc.constraint_name
|
||||
AND ac.r_owner = rem.owner(+)
|
||||
AND ac.r_constraint_name = rem.constraint_name(+)
|
||||
AND (rem.position IS NULL or loc.position=rem.position)
|
||||
ORDER BY ac.constraint_name, loc.position""" % {'dblink': dblink}),
|
||||
table_name=table_name, owner=schema)
|
||||
params = {'table_name': table_name}
|
||||
|
||||
text = \
|
||||
"SELECT"\
|
||||
"\nac.constraint_name,"\
|
||||
"\nac.constraint_type,"\
|
||||
"\nloc.column_name AS local_column,"\
|
||||
"\nrem.table_name AS remote_table,"\
|
||||
"\nrem.column_name AS remote_column,"\
|
||||
"\nrem.owner AS remote_owner,"\
|
||||
"\nloc.position as loc_pos,"\
|
||||
"\nrem.position as rem_pos"\
|
||||
"\nFROM all_constraints%(dblink)s ac,"\
|
||||
"\nall_cons_columns%(dblink)s loc,"\
|
||||
"\nall_cons_columns%(dblink)s rem"\
|
||||
"\nWHERE ac.table_name = :table_name"\
|
||||
"\nAND ac.constraint_type IN ('R','P')"
|
||||
|
||||
if schema is not None:
|
||||
params['owner'] = schema
|
||||
text += "\nAND ac.owner = :owner"
|
||||
|
||||
text += \
|
||||
"\nAND ac.owner = loc.owner"\
|
||||
"\nAND ac.constraint_name = loc.constraint_name"\
|
||||
"\nAND ac.r_owner = rem.owner(+)"\
|
||||
"\nAND ac.r_constraint_name = rem.constraint_name(+)"\
|
||||
"\nAND (rem.position IS NULL or loc.position=rem.position)"\
|
||||
"\nORDER BY ac.constraint_name, loc.position"
|
||||
|
||||
text = text % {'dblink': dblink}
|
||||
rp = connection.execute(sql.text(text), **params)
|
||||
constraint_data = rp.fetchall()
|
||||
return constraint_data
|
||||
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
"""
|
||||
|
||||
kw arguments can be:
|
||||
|
||||
oracle_resolve_synonyms
|
||||
|
||||
dblink
|
||||
|
||||
"""
|
||||
return self._get_primary_keys(connection, table_name, schema, **kw)[0]
|
||||
|
||||
@reflection.cache
|
||||
def _get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
resolve_synonyms = kw.get('oracle_resolve_synonyms', False)
|
||||
dblink = kw.get('dblink', '')
|
||||
info_cache = kw.get('info_cache')
|
||||
@@ -1037,22 +1133,13 @@ class OracleDialect(default.DefaultDialect):
|
||||
info_cache=kw.get('info_cache'))
|
||||
|
||||
for row in constraint_data:
|
||||
#print "ROW:" , row
|
||||
(cons_name, cons_type, local_column, remote_table, remote_column, remote_owner) = \
|
||||
row[0:2] + tuple([self.normalize_name(x) for x in row[2:6]])
|
||||
if cons_type == 'P':
|
||||
if constraint_name is None:
|
||||
constraint_name = self.normalize_name(cons_name)
|
||||
pkeys.append(local_column)
|
||||
return pkeys, constraint_name
|
||||
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
cols, name = self._get_primary_keys(connection, table_name, schema=schema, **kw)
|
||||
|
||||
return {
|
||||
'constrained_columns':cols,
|
||||
'name':name
|
||||
}
|
||||
return {'constrained_columns': pkeys, 'name': constraint_name}
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
@@ -1066,7 +1153,7 @@ class OracleDialect(default.DefaultDialect):
|
||||
|
||||
"""
|
||||
|
||||
requested_schema = schema # to check later on
|
||||
requested_schema = schema # to check later on
|
||||
resolve_synonyms = kw.get('oracle_resolve_synonyms', False)
|
||||
dblink = kw.get('dblink', '')
|
||||
info_cache = kw.get('info_cache')
|
||||
@@ -1082,11 +1169,11 @@ class OracleDialect(default.DefaultDialect):
|
||||
|
||||
def fkey_rec():
|
||||
return {
|
||||
'name' : None,
|
||||
'constrained_columns' : [],
|
||||
'referred_schema' : None,
|
||||
'referred_table' : None,
|
||||
'referred_columns' : []
|
||||
'name': None,
|
||||
'constrained_columns': [],
|
||||
'referred_schema': None,
|
||||
'referred_table': None,
|
||||
'referred_columns': []
|
||||
}
|
||||
|
||||
fkeys = util.defaultdict(fkey_rec)
|
||||
@@ -1101,7 +1188,7 @@ class OracleDialect(default.DefaultDialect):
|
||||
util.warn(
|
||||
("Got 'None' querying 'table_name' from "
|
||||
"all_cons_columns%(dblink)s - does the user have "
|
||||
"proper rights to the table?") % {'dblink':dblink})
|
||||
"proper rights to the table?") % {'dblink': dblink})
|
||||
continue
|
||||
|
||||
rec = fkeys[cons_name]
|
||||
@@ -1128,7 +1215,7 @@ class OracleDialect(default.DefaultDialect):
|
||||
local_cols.append(local_column)
|
||||
remote_cols.append(remote_column)
|
||||
|
||||
return fkeys.values()
|
||||
return list(fkeys.values())
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None,
|
||||
@@ -1138,25 +1225,25 @@ class OracleDialect(default.DefaultDialect):
|
||||
self._prepare_reflection_args(connection, view_name, schema,
|
||||
resolve_synonyms, dblink,
|
||||
info_cache=info_cache)
|
||||
s = sql.text("""
|
||||
SELECT text FROM all_views
|
||||
WHERE owner = :schema
|
||||
AND view_name = :view_name
|
||||
""")
|
||||
rp = connection.execute(s,
|
||||
view_name=view_name, schema=schema).scalar()
|
||||
|
||||
params = {'view_name': view_name}
|
||||
text = "SELECT text FROM all_views WHERE view_name=:view_name"
|
||||
|
||||
if schema is not None:
|
||||
text += " AND owner = :schema"
|
||||
params['schema'] = schema
|
||||
|
||||
rp = connection.execute(sql.text(text), **params).scalar()
|
||||
if rp:
|
||||
return rp.decode(self.encoding)
|
||||
if util.py2k:
|
||||
rp = rp.decode(self.encoding)
|
||||
return rp
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class _OuterJoinColumn(sql.ClauseElement):
|
||||
__visit_name__ = 'outer_join_column'
|
||||
|
||||
def __init__(self, column):
|
||||
self.column = column
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,48 +1,57 @@
|
||||
# oracle/cx_oracle.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the Oracle database via the cx_oracle driver.
|
||||
"""
|
||||
|
||||
Driver
|
||||
------
|
||||
.. dialect:: oracle+cx_oracle
|
||||
:name: cx-Oracle
|
||||
:dbapi: cx_oracle
|
||||
:connectstring: oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://cx-oracle.sourceforge.net/
|
||||
|
||||
The Oracle dialect uses the cx_oracle driver, available at
|
||||
http://cx-oracle.sourceforge.net/ . The dialect has several behaviors
|
||||
which are specifically tailored towards compatibility with this module.
|
||||
Version 5.0 or greater is **strongly** recommended, as SQLAlchemy makes
|
||||
extensive use of the cx_oracle output converters for numeric and
|
||||
string conversions.
|
||||
Additional Connect Arguments
|
||||
----------------------------
|
||||
|
||||
Connecting
|
||||
----------
|
||||
When connecting with ``dbname`` 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.
|
||||
|
||||
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
|
||||
host, port, and dbname tokens are converted to a TNS name using the cx_oracle
|
||||
: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 :func:`.create_engine()` are:
|
||||
|
||||
Additional arguments which may be specified either as query string arguments on the
|
||||
URL, or as keyword arguments to :func:`~sqlalchemy.create_engine()` are:
|
||||
* allow_twophase - enable two-phase transactions. Defaults to ``True``.
|
||||
|
||||
* *allow_twophase* - enable two-phase transactions. Defaults to ``True``.
|
||||
|
||||
* *arraysize* - set the cx_oracle.arraysize value on cursors, in SQLAlchemy
|
||||
* arraysize - set the cx_oracle.arraysize value on cursors, in SQLAlchemy
|
||||
it defaults to 50. See the section on "LOB Objects" below.
|
||||
|
||||
* *auto_convert_lobs* - defaults to True, see the section on LOB objects.
|
||||
* auto_convert_lobs - defaults to True, see the section on LOB objects.
|
||||
|
||||
* *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``.
|
||||
* 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``. Specific types
|
||||
can be excluded from this process using the ``exclude_setinputsizes``
|
||||
parameter.
|
||||
|
||||
* *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.
|
||||
* exclude_setinputsizes - a tuple or list of string DBAPI type names to
|
||||
be excluded from the "auto setinputsizes" feature. The type names here
|
||||
must match DBAPI types that are found in the "cx_Oracle" module namespace,
|
||||
such as cx_Oracle.UNICODE, cx_Oracle.NCLOB, etc. Defaults to
|
||||
``(STRING, UNICODE)``.
|
||||
|
||||
* *threaded* - enable multithreaded access to cx_oracle connections. Defaults
|
||||
to ``True``. Note that this is the opposite default of cx_oracle itself.
|
||||
.. versionadded:: 0.8 specific DBAPI types can be excluded from the
|
||||
auto_setinputsizes feature via the exclude_setinputsizes attribute.
|
||||
|
||||
* 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.
|
||||
|
||||
* threaded - enable multithreaded access to cx_oracle connections. Defaults
|
||||
to ``True``. Note that this is the opposite default of the cx_Oracle DBAPI
|
||||
itself.
|
||||
|
||||
Unicode
|
||||
-------
|
||||
@@ -56,6 +65,27 @@ of the encoding to be used.
|
||||
Note that this behavior is disabled when Oracle 8 is detected, as it has been
|
||||
observed that issues remain when passing Python unicodes to cx_oracle with Oracle 8.
|
||||
|
||||
.. _cx_oracle_returning:
|
||||
|
||||
RETURNING Support
|
||||
-----------------
|
||||
|
||||
cx_oracle supports a limited subset of Oracle's already limited RETURNING support.
|
||||
Typically, results can only be guaranteed for at most one column being returned;
|
||||
this is the typical case when SQLAlchemy uses RETURNING to get just the value of a
|
||||
primary-key-associated sequence value. Additional column expressions will
|
||||
cause problems in a non-determinative way, due to cx_oracle's lack of support for
|
||||
the OCI_DATA_AT_EXEC API which is required for more complex RETURNING scenarios.
|
||||
|
||||
.. seealso::
|
||||
|
||||
http://docs.oracle.com/cd/B10501_01/appdev.920/a96584/oci05bnd.htm#420693 - OCI documentation for RETURNING
|
||||
|
||||
http://sourceforge.net/mailarchive/message.php?msg_id=31338136 - cx_oracle developer commentary
|
||||
|
||||
|
||||
|
||||
|
||||
LOB Objects
|
||||
-----------
|
||||
|
||||
@@ -66,7 +96,7 @@ like result.fetchmany() and result.fetchall(). This means that by default, LOB
|
||||
objects are fully fetched unconditionally by SQLAlchemy, and the linkage to a live
|
||||
cursor is broken.
|
||||
|
||||
To disable this processing, pass ``auto_convert_lobs=False`` to :func:`create_engine()`.
|
||||
To disable this processing, pass ``auto_convert_lobs=False`` to :func:`.create_engine()`.
|
||||
|
||||
Two Phase Transaction Support
|
||||
-----------------------------
|
||||
@@ -99,7 +129,7 @@ the application can make one of several choices:
|
||||
|
||||
* For ad-hoc two-phase operations without disabling pooling, the DBAPI
|
||||
connection in use can be evicted from the connection pool using the
|
||||
:class:`.Connection.detach` method.
|
||||
:meth:`.Connection.detach` method.
|
||||
|
||||
.. versionchanged:: 0.8.0b2,0.7.10
|
||||
Support for cx_oracle prepared transactions has been implemented
|
||||
@@ -173,16 +203,18 @@ a period "." as the decimal character.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, \
|
||||
OracleExecutionContext
|
||||
from sqlalchemy.dialects.oracle import base as oracle
|
||||
from sqlalchemy.engine import base
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .base import OracleCompiler, OracleDialect, OracleExecutionContext
|
||||
from . import base as oracle
|
||||
from ...engine import result as _result
|
||||
from sqlalchemy import types as sqltypes, util, exc, processors
|
||||
import random
|
||||
import collections
|
||||
from sqlalchemy.util.compat import decimal
|
||||
import decimal
|
||||
import re
|
||||
|
||||
|
||||
class _OracleNumeric(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
# cx_oracle accepts Decimal objects and floats
|
||||
@@ -200,10 +232,8 @@ class _OracleNumeric(sqltypes.Numeric):
|
||||
|
||||
if dialect.supports_native_decimal:
|
||||
if self.asdecimal:
|
||||
if self.scale is None:
|
||||
fstring = "%.10f"
|
||||
else:
|
||||
fstring = "%%.%df" % self.scale
|
||||
fstring = "%%.%df" % self._effective_decimal_return_scale
|
||||
|
||||
def to_decimal(value):
|
||||
if value is None:
|
||||
return None
|
||||
@@ -211,6 +241,7 @@ class _OracleNumeric(sqltypes.Numeric):
|
||||
return value
|
||||
else:
|
||||
return decimal.Decimal(fstring % value)
|
||||
|
||||
return to_decimal
|
||||
else:
|
||||
if self.precision is None and self.scale is None:
|
||||
@@ -226,6 +257,7 @@ class _OracleNumeric(sqltypes.Numeric):
|
||||
return super(_OracleNumeric, self).\
|
||||
result_processor(dialect, coltype)
|
||||
|
||||
|
||||
class _OracleDate(sqltypes.Date):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
@@ -238,6 +270,7 @@ class _OracleDate(sqltypes.Date):
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class _LOBMixin(object):
|
||||
def result_processor(self, dialect, coltype):
|
||||
if not dialect.auto_convert_lobs:
|
||||
@@ -251,38 +284,40 @@ class _LOBMixin(object):
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class _NativeUnicodeMixin(object):
|
||||
# Py3K
|
||||
#pass
|
||||
# Py2K
|
||||
def bind_processor(self, dialect):
|
||||
if dialect._cx_oracle_with_unicode:
|
||||
def process(value):
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
return unicode(value)
|
||||
return process
|
||||
else:
|
||||
return super(_NativeUnicodeMixin, self).bind_processor(dialect)
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
def bind_processor(self, dialect):
|
||||
if dialect._cx_oracle_with_unicode:
|
||||
def process(value):
|
||||
if value is None:
|
||||
return value
|
||||
else:
|
||||
return unicode(value)
|
||||
return process
|
||||
else:
|
||||
return super(_NativeUnicodeMixin, self).bind_processor(dialect)
|
||||
|
||||
# we apply a connection output handler that returns
|
||||
# unicode in all cases, so the "native_unicode" flag
|
||||
# will be set for the default String.result_processor.
|
||||
|
||||
|
||||
class _OracleChar(_NativeUnicodeMixin, sqltypes.CHAR):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.FIXED_CHAR
|
||||
|
||||
|
||||
class _OracleNVarChar(_NativeUnicodeMixin, sqltypes.NVARCHAR):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return getattr(dbapi, 'UNICODE', dbapi.STRING)
|
||||
|
||||
|
||||
class _OracleText(_LOBMixin, sqltypes.Text):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.CLOB
|
||||
|
||||
|
||||
class _OracleLong(oracle.LONG):
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
@@ -293,6 +328,7 @@ class _OracleLong(oracle.LONG):
|
||||
class _OracleString(_NativeUnicodeMixin, sqltypes.String):
|
||||
pass
|
||||
|
||||
|
||||
class _OracleUnicodeText(_LOBMixin, _NativeUnicodeMixin, sqltypes.UnicodeText):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.NCLOB
|
||||
@@ -311,6 +347,7 @@ class _OracleUnicodeText(_LOBMixin, _NativeUnicodeMixin, sqltypes.UnicodeText):
|
||||
return string_processor(lob_processor(value))
|
||||
return process
|
||||
|
||||
|
||||
class _OracleInteger(sqltypes.Integer):
|
||||
def result_processor(self, dialect, coltype):
|
||||
def to_int(val):
|
||||
@@ -319,6 +356,7 @@ class _OracleInteger(sqltypes.Integer):
|
||||
return val
|
||||
return to_int
|
||||
|
||||
|
||||
class _OracleBinary(_LOBMixin, sqltypes.LargeBinary):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.BLOB
|
||||
@@ -326,20 +364,26 @@ class _OracleBinary(_LOBMixin, sqltypes.LargeBinary):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
|
||||
class _OracleInterval(oracle.INTERVAL):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.INTERVAL
|
||||
|
||||
|
||||
class _OracleRaw(oracle.RAW):
|
||||
pass
|
||||
|
||||
|
||||
class _OracleRowid(oracle.ROWID):
|
||||
def get_dbapi_type(self, dbapi):
|
||||
return dbapi.ROWID
|
||||
|
||||
|
||||
class OracleCompiler_cx_oracle(OracleCompiler):
|
||||
def bindparam_string(self, name, **kw):
|
||||
if self.preparer._bindparam_requires_quotes(name):
|
||||
quote = getattr(name, 'quote', None)
|
||||
if quote is True or quote is not False and \
|
||||
self.preparer._bindparam_requires_quotes(name):
|
||||
quoted_name = '"%s"' % name
|
||||
self._quoted_bind_names[name] = quoted_name
|
||||
return OracleCompiler.bindparam_string(self, quoted_name, **kw)
|
||||
@@ -375,7 +419,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
# on String, including that outparams/RETURNING
|
||||
# breaks for varchars
|
||||
self.set_input_sizes(quoted_bind_names,
|
||||
exclude_types=self.dialect._cx_oracle_exclude_setinputsizes
|
||||
exclude_types=self.dialect.exclude_setinputsizes
|
||||
)
|
||||
|
||||
# if a single execute, check for outparams
|
||||
@@ -387,11 +431,12 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
if not hasattr(self, 'out_parameters'):
|
||||
self.out_parameters = {}
|
||||
if dbtype is None:
|
||||
raise exc.InvalidRequestError("Cannot create out parameter for parameter "
|
||||
"%r - it's type %r is not supported by"
|
||||
" cx_oracle" %
|
||||
(name, bindparam.type)
|
||||
)
|
||||
raise exc.InvalidRequestError(
|
||||
"Cannot create out parameter for parameter "
|
||||
"%r - it's type %r is not supported by"
|
||||
" cx_oracle" %
|
||||
(bindparam.key, bindparam.type)
|
||||
)
|
||||
name = self.compiled.bind_names[bindparam]
|
||||
self.out_parameters[name] = self.cursor.var(dbtype)
|
||||
self.parameters[0][quoted_bind_names.get(name, name)] = \
|
||||
@@ -417,10 +462,10 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
for column in self.cursor.description:
|
||||
type_code = column[1]
|
||||
if type_code in self.dialect._cx_oracle_binary_types:
|
||||
result = base.BufferedColumnResultProxy(self)
|
||||
result = _result.BufferedColumnResultProxy(self)
|
||||
|
||||
if result is None:
|
||||
result = base.ResultProxy(self)
|
||||
result = _result.ResultProxy(self)
|
||||
|
||||
if hasattr(self, 'out_parameters'):
|
||||
if self.compiled_parameters is not None and \
|
||||
@@ -448,6 +493,7 @@ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class OracleExecutionContext_cx_oracle_with_unicode(OracleExecutionContext_cx_oracle):
|
||||
"""Support WITH_UNICODE in Python 2.xx.
|
||||
|
||||
@@ -463,13 +509,14 @@ class OracleExecutionContext_cx_oracle_with_unicode(OracleExecutionContext_cx_or
|
||||
"""
|
||||
def __init__(self, *arg, **kw):
|
||||
OracleExecutionContext_cx_oracle.__init__(self, *arg, **kw)
|
||||
self.statement = unicode(self.statement)
|
||||
self.statement = util.text_type(self.statement)
|
||||
|
||||
def _execute_scalar(self, stmt):
|
||||
return super(OracleExecutionContext_cx_oracle_with_unicode, self).\
|
||||
_execute_scalar(unicode(stmt))
|
||||
_execute_scalar(util.text_type(stmt))
|
||||
|
||||
class ReturningResultProxy(base.FullyBufferedResultProxy):
|
||||
|
||||
class ReturningResultProxy(_result.FullyBufferedResultProxy):
|
||||
"""Result proxy which stuffs the _returning clause + outparams into the fetch."""
|
||||
|
||||
def __init__(self, context, returning_params):
|
||||
@@ -478,19 +525,16 @@ class ReturningResultProxy(base.FullyBufferedResultProxy):
|
||||
|
||||
def _cursor_description(self):
|
||||
returning = self.context.compiled.returning
|
||||
|
||||
ret = []
|
||||
for c in returning:
|
||||
if hasattr(c, 'name'):
|
||||
ret.append((c.name, c.type))
|
||||
else:
|
||||
ret.append((c.anon_label, c.type))
|
||||
return ret
|
||||
return [
|
||||
("ret_%d" % i, None)
|
||||
for i, col in enumerate(returning)
|
||||
]
|
||||
|
||||
def _buffer_rows(self):
|
||||
return collections.deque([tuple(self._returning_params["ret_%d" % i]
|
||||
for i, c in enumerate(self._returning_params))])
|
||||
|
||||
|
||||
class OracleDialect_cx_oracle(OracleDialect):
|
||||
execution_ctx_cls = OracleExecutionContext_cx_oracle
|
||||
statement_compiler = OracleCompiler_cx_oracle
|
||||
@@ -499,34 +543,36 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
|
||||
colspecs = colspecs = {
|
||||
sqltypes.Numeric: _OracleNumeric,
|
||||
sqltypes.Date : _OracleDate, # generic type, assume datetime.date is desired
|
||||
sqltypes.Date: _OracleDate, # generic type, assume datetime.date is desired
|
||||
oracle.DATE: oracle.DATE, # non generic type - passthru
|
||||
sqltypes.LargeBinary : _OracleBinary,
|
||||
sqltypes.Boolean : oracle._OracleBoolean,
|
||||
sqltypes.Interval : _OracleInterval,
|
||||
oracle.INTERVAL : _OracleInterval,
|
||||
sqltypes.Text : _OracleText,
|
||||
sqltypes.String : _OracleString,
|
||||
sqltypes.UnicodeText : _OracleUnicodeText,
|
||||
sqltypes.CHAR : _OracleChar,
|
||||
sqltypes.LargeBinary: _OracleBinary,
|
||||
sqltypes.Boolean: oracle._OracleBoolean,
|
||||
sqltypes.Interval: _OracleInterval,
|
||||
oracle.INTERVAL: _OracleInterval,
|
||||
sqltypes.Text: _OracleText,
|
||||
sqltypes.String: _OracleString,
|
||||
sqltypes.UnicodeText: _OracleUnicodeText,
|
||||
sqltypes.CHAR: _OracleChar,
|
||||
|
||||
# a raw LONG is a text type, but does *not*
|
||||
# get the LobMixin with cx_oracle.
|
||||
oracle.LONG: _OracleLong,
|
||||
|
||||
sqltypes.Integer : _OracleInteger, # this is only needed for OUT parameters.
|
||||
# it would be nice if we could not use it otherwise.
|
||||
# this is only needed for OUT parameters.
|
||||
# it would be nice if we could not use it otherwise.
|
||||
sqltypes.Integer: _OracleInteger,
|
||||
|
||||
oracle.RAW: _OracleRaw,
|
||||
sqltypes.Unicode: _OracleNVarChar,
|
||||
sqltypes.NVARCHAR : _OracleNVarChar,
|
||||
sqltypes.NVARCHAR: _OracleNVarChar,
|
||||
oracle.ROWID: _OracleRowid,
|
||||
}
|
||||
|
||||
|
||||
execute_sequence_format = list
|
||||
|
||||
def __init__(self,
|
||||
auto_setinputsizes=True,
|
||||
exclude_setinputsizes=("STRING", "UNICODE"),
|
||||
auto_convert_lobs=True,
|
||||
threaded=True,
|
||||
allow_twophase=True,
|
||||
@@ -536,22 +582,25 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
self.threaded = threaded
|
||||
self.arraysize = arraysize
|
||||
self.allow_twophase = allow_twophase
|
||||
self.supports_timestamp = self.dbapi is None or hasattr(self.dbapi, 'TIMESTAMP' )
|
||||
self.supports_timestamp = self.dbapi is None or \
|
||||
hasattr(self.dbapi, 'TIMESTAMP')
|
||||
self.auto_setinputsizes = auto_setinputsizes
|
||||
self.auto_convert_lobs = auto_convert_lobs
|
||||
|
||||
if hasattr(self.dbapi, 'version'):
|
||||
self.cx_oracle_ver = tuple([int(x) for x in self.dbapi.version.split('.')])
|
||||
self.cx_oracle_ver = tuple([int(x) for x in
|
||||
self.dbapi.version.split('.')])
|
||||
else:
|
||||
self.cx_oracle_ver = (0, 0, 0)
|
||||
|
||||
def types(*names):
|
||||
return set([
|
||||
getattr(self.dbapi, name, None) for name in names
|
||||
]).difference([None])
|
||||
return set(
|
||||
getattr(self.dbapi, name, None) for name in names
|
||||
).difference([None])
|
||||
|
||||
self._cx_oracle_exclude_setinputsizes = types("STRING", "UNICODE")
|
||||
self._cx_oracle_string_types = types("STRING", "UNICODE", "NCLOB", "CLOB")
|
||||
self.exclude_setinputsizes = types(*(exclude_setinputsizes or ()))
|
||||
self._cx_oracle_string_types = types("STRING", "UNICODE",
|
||||
"NCLOB", "CLOB")
|
||||
self._cx_oracle_unicode_types = types("UNICODE", "NCLOB")
|
||||
self._cx_oracle_binary_types = types("BFILE", "CLOB", "NCLOB", "BLOB")
|
||||
self.supports_unicode_binds = self.cx_oracle_ver >= (5, 0)
|
||||
@@ -573,19 +622,23 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
self.supports_unicode_statements = True
|
||||
self.supports_unicode_binds = True
|
||||
self._cx_oracle_with_unicode = True
|
||||
# Py2K
|
||||
# There's really no reason to run with WITH_UNICODE under Python 2.x.
|
||||
# Give the user a hint.
|
||||
util.warn("cx_Oracle is compiled under Python 2.xx using the "
|
||||
"WITH_UNICODE flag. Consider recompiling cx_Oracle without "
|
||||
"this flag, which is in no way necessary for full support of Unicode. "
|
||||
"Otherwise, all string-holding bind parameters must "
|
||||
"be explicitly typed using SQLAlchemy's String type or one of its subtypes,"
|
||||
"or otherwise be passed as Python unicode. Plain Python strings "
|
||||
"passed as bind parameters will be silently corrupted by cx_Oracle."
|
||||
)
|
||||
self.execution_ctx_cls = OracleExecutionContext_cx_oracle_with_unicode
|
||||
# end Py2K
|
||||
|
||||
if util.py2k:
|
||||
# There's really no reason to run with WITH_UNICODE under Python 2.x.
|
||||
# Give the user a hint.
|
||||
util.warn(
|
||||
"cx_Oracle is compiled under Python 2.xx using the "
|
||||
"WITH_UNICODE flag. Consider recompiling cx_Oracle "
|
||||
"without this flag, which is in no way necessary for full "
|
||||
"support of Unicode. Otherwise, all string-holding bind "
|
||||
"parameters must be explicitly typed using SQLAlchemy's "
|
||||
"String type or one of its subtypes,"
|
||||
"or otherwise be passed as Python unicode. "
|
||||
"Plain Python strings passed as bind parameters will be "
|
||||
"silently corrupted by cx_Oracle."
|
||||
)
|
||||
self.execution_ctx_cls = \
|
||||
OracleExecutionContext_cx_oracle_with_unicode
|
||||
else:
|
||||
self._cx_oracle_with_unicode = False
|
||||
|
||||
@@ -603,9 +656,10 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
self.dbapi.BLOB: oracle.BLOB(),
|
||||
self.dbapi.BINARY: oracle.RAW(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
cx_Oracle = __import__('cx_Oracle')
|
||||
import cx_Oracle
|
||||
return cx_Oracle
|
||||
|
||||
def initialize(self, connection):
|
||||
@@ -668,6 +722,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
return
|
||||
|
||||
cx_Oracle = self.dbapi
|
||||
|
||||
def output_type_handler(cursor, name, defaultType,
|
||||
size, precision, scale):
|
||||
# convert all NUMBER with precision + positive scale to Decimal
|
||||
@@ -695,7 +750,7 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
arraysize=cursor.arraysize)
|
||||
# allow all strings to come back natively as Unicode
|
||||
elif defaultType in (cx_Oracle.STRING, cx_Oracle.FIXED_CHAR):
|
||||
return cursor.var(unicode, size, cursor.arraysize)
|
||||
return cursor.var(util.text_type, size, cursor.arraysize)
|
||||
|
||||
def on_connect(conn):
|
||||
conn.outputtypehandler = output_type_handler
|
||||
@@ -730,20 +785,19 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
twophase=self.allow_twophase,
|
||||
)
|
||||
|
||||
# Py2K
|
||||
if self._cx_oracle_with_unicode:
|
||||
for k, v in opts.items():
|
||||
if isinstance(v, str):
|
||||
opts[k] = unicode(v)
|
||||
else:
|
||||
for k, v in opts.items():
|
||||
if isinstance(v, unicode):
|
||||
opts[k] = str(v)
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
if self._cx_oracle_with_unicode:
|
||||
for k, v in opts.items():
|
||||
if isinstance(v, str):
|
||||
opts[k] = unicode(v)
|
||||
else:
|
||||
for k, v in opts.items():
|
||||
if isinstance(v, unicode):
|
||||
opts[k] = str(v)
|
||||
|
||||
if 'mode' in url.query:
|
||||
opts['mode'] = url.query['mode']
|
||||
if isinstance(opts['mode'], basestring):
|
||||
if isinstance(opts['mode'], util.string_types):
|
||||
mode = opts['mode'].upper()
|
||||
if mode == 'SYSDBA':
|
||||
opts['mode'] = self.dbapi.SYSDBA
|
||||
@@ -769,8 +823,9 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
# ORA-03113: end-of-file on communication channel
|
||||
# ORA-03135: connection lost contact
|
||||
# ORA-01033: ORACLE initialization or shutdown in progress
|
||||
# ORA-02396: exceeded maximum idle time, please connect again
|
||||
# TODO: Others ?
|
||||
return error.code in (28, 3114, 3113, 3135, 1033)
|
||||
return error.code in (28, 3114, 3113, 3135, 1033, 2396)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -783,6 +838,11 @@ class OracleDialect_cx_oracle(OracleDialect):
|
||||
id = random.randint(0, 2 ** 128)
|
||||
return (0x1234, "%032x" % id, "%032x" % 9)
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
if isinstance(parameters, tuple):
|
||||
parameters = list(parameters)
|
||||
cursor.executemany(statement, parameters)
|
||||
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
connection.connection.begin(*xid)
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# oracle/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the Oracle database via the zxjdbc JDBC connector.
|
||||
|
||||
JDBC Driver
|
||||
-----------
|
||||
|
||||
The official Oracle JDBC driver is at
|
||||
http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html.
|
||||
"""
|
||||
.. dialect:: oracle+zxjdbc
|
||||
:name: zxJDBC for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: oracle+zxjdbc://user:pass@host/dbname
|
||||
:driverurl: http://www.oracle.com/technology/software/tech/java/sqlj_jdbc/index.html.
|
||||
|
||||
"""
|
||||
import decimal
|
||||
@@ -19,12 +18,13 @@ import re
|
||||
from sqlalchemy import sql, types as sqltypes, util
|
||||
from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.oracle.base import OracleCompiler, OracleDialect, OracleExecutionContext
|
||||
from sqlalchemy.engine import base, default
|
||||
from sqlalchemy.engine import result as _result
|
||||
from sqlalchemy.sql import expression
|
||||
import collections
|
||||
|
||||
SQLException = zxJDBC = None
|
||||
|
||||
|
||||
class _ZxJDBCDate(sqltypes.Date):
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
@@ -78,7 +78,7 @@ class OracleCompiler_zxjdbc(OracleCompiler):
|
||||
self.binds[bindparam.key] = bindparam
|
||||
binds.append(self.bindparam_string(self._truncate_bindparam(bindparam)))
|
||||
|
||||
return 'RETURNING ' + ', '.join(columns) + " INTO " + ", ".join(binds)
|
||||
return 'RETURNING ' + ', '.join(columns) + " INTO " + ", ".join(binds)
|
||||
|
||||
|
||||
class OracleExecutionContext_zxjdbc(OracleExecutionContext):
|
||||
@@ -95,8 +95,8 @@ class OracleExecutionContext_zxjdbc(OracleExecutionContext):
|
||||
try:
|
||||
try:
|
||||
rrs = self.statement.__statement__.getReturnResultSet()
|
||||
rrs.next()
|
||||
except SQLException, sqle:
|
||||
next(rrs)
|
||||
except SQLException as sqle:
|
||||
msg = '%s [SQLCode: %d]' % (sqle.getMessage(), sqle.getErrorCode())
|
||||
if sqle.getSQLState() is not None:
|
||||
msg += ' [SQLState: %s]' % sqle.getSQLState()
|
||||
@@ -113,7 +113,7 @@ class OracleExecutionContext_zxjdbc(OracleExecutionContext):
|
||||
pass
|
||||
self.statement.close()
|
||||
|
||||
return base.ResultProxy(self)
|
||||
return _result.ResultProxy(self)
|
||||
|
||||
def create_cursor(self):
|
||||
cursor = self._dbapi_connection.cursor()
|
||||
@@ -121,7 +121,7 @@ class OracleExecutionContext_zxjdbc(OracleExecutionContext):
|
||||
return cursor
|
||||
|
||||
|
||||
class ReturningResultProxy(base.FullyBufferedResultProxy):
|
||||
class ReturningResultProxy(_result.FullyBufferedResultProxy):
|
||||
|
||||
"""ResultProxy backed by the RETURNING ResultSet results."""
|
||||
|
||||
@@ -178,7 +178,7 @@ class OracleDialect_zxjdbc(ZxJDBCConnector, OracleDialect):
|
||||
colspecs = util.update_copy(
|
||||
OracleDialect.colspecs,
|
||||
{
|
||||
sqltypes.Date : _ZxJDBCDate,
|
||||
sqltypes.Date: _ZxJDBCDate,
|
||||
sqltypes.Numeric: _ZxJDBCNumeric
|
||||
}
|
||||
)
|
||||
@@ -189,17 +189,19 @@ class OracleDialect_zxjdbc(ZxJDBCConnector, OracleDialect):
|
||||
from java.sql import SQLException
|
||||
from com.ziclix.python.sql import zxJDBC
|
||||
from com.ziclix.python.sql.handler import OracleDataHandler
|
||||
class OracleReturningDataHandler(OracleDataHandler):
|
||||
|
||||
class OracleReturningDataHandler(OracleDataHandler):
|
||||
"""zxJDBC DataHandler that specially handles ReturningParam."""
|
||||
|
||||
def setJDBCObject(self, statement, index, object, dbtype=None):
|
||||
if type(object) is ReturningParam:
|
||||
statement.registerReturnParameter(index, object.type)
|
||||
elif dbtype is None:
|
||||
OracleDataHandler.setJDBCObject(self, statement, index, object)
|
||||
OracleDataHandler.setJDBCObject(
|
||||
self, statement, index, object)
|
||||
else:
|
||||
OracleDataHandler.setJDBCObject(self, statement, index, object, dbtype)
|
||||
OracleDataHandler.setJDBCObject(
|
||||
self, statement, index, object, dbtype)
|
||||
self.DataHandler = OracleReturningDataHandler
|
||||
|
||||
def initialize(self, connection):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# dialects/postgres.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
# postgresql/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.postgresql import base, psycopg2, pg8000, pypostgresql, zxjdbc
|
||||
from . import base, psycopg2, pg8000, pypostgresql, zxjdbc
|
||||
|
||||
base.dialect = psycopg2.dialect
|
||||
|
||||
from sqlalchemy.dialects.postgresql.base import \
|
||||
INTEGER, BIGINT, SMALLINT, VARCHAR, CHAR, TEXT, NUMERIC, FLOAT, REAL, INET, \
|
||||
CIDR, UUID, BIT, MACADDR, DOUBLE_PRECISION, TIMESTAMP, TIME,\
|
||||
DATE, BYTEA, BOOLEAN, INTERVAL, ARRAY, ENUM, dialect
|
||||
from .base import \
|
||||
INTEGER, BIGINT, SMALLINT, VARCHAR, CHAR, TEXT, NUMERIC, FLOAT, REAL, \
|
||||
INET, CIDR, UUID, BIT, MACADDR, DOUBLE_PRECISION, TIMESTAMP, TIME, \
|
||||
DATE, BYTEA, BOOLEAN, INTERVAL, ARRAY, ENUM, dialect, array, Any, All, \
|
||||
TSVECTOR
|
||||
from .constraints import ExcludeConstraint
|
||||
from .hstore import HSTORE, hstore
|
||||
from .json import JSON, JSONElement
|
||||
from .ranges import INT4RANGE, INT8RANGE, NUMRANGE, DATERANGE, TSRANGE, \
|
||||
TSTZRANGE
|
||||
|
||||
__all__ = (
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'VARCHAR', 'CHAR', 'TEXT', 'NUMERIC', 'FLOAT', 'REAL', 'INET',
|
||||
'CIDR', 'UUID', 'BIT', 'MACADDR', 'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME',
|
||||
'DATE', 'BYTEA', 'BOOLEAN', 'INTERVAL', 'ARRAY', 'ENUM', 'dialect'
|
||||
'INTEGER', 'BIGINT', 'SMALLINT', 'VARCHAR', 'CHAR', 'TEXT', 'NUMERIC',
|
||||
'FLOAT', 'REAL', 'INET', 'CIDR', 'UUID', 'BIT', 'MACADDR',
|
||||
'DOUBLE_PRECISION', 'TIMESTAMP', 'TIME', 'DATE', 'BYTEA', 'BOOLEAN',
|
||||
'INTERVAL', 'ARRAY', 'ENUM', 'dialect', 'Any', 'All', 'array', 'HSTORE',
|
||||
'hstore', 'INT4RANGE', 'INT8RANGE', 'NUMRANGE', 'DATERANGE',
|
||||
'TSRANGE', 'TSTZRANGE', 'json', 'JSON', 'JSONElement'
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2013-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
from sqlalchemy.schema import ColumnCollectionConstraint
|
||||
from sqlalchemy.sql import expression
|
||||
|
||||
class ExcludeConstraint(ColumnCollectionConstraint):
|
||||
"""A table-level EXCLUDE constraint.
|
||||
|
||||
Defines an EXCLUDE constraint as described in the `postgres
|
||||
documentation`__.
|
||||
|
||||
__ http://www.postgresql.org/docs/9.0/static/sql-createtable.html#SQL-CREATETABLE-EXCLUDE
|
||||
"""
|
||||
|
||||
__visit_name__ = 'exclude_constraint'
|
||||
|
||||
where = None
|
||||
|
||||
def __init__(self, *elements, **kw):
|
||||
"""
|
||||
:param \*elements:
|
||||
A sequence of two tuples of the form ``(column, operator)`` where
|
||||
column must be a column name or Column object and operator must
|
||||
be a string containing the operator to use.
|
||||
|
||||
:param name:
|
||||
Optional, the in-database name of this constraint.
|
||||
|
||||
:param deferrable:
|
||||
Optional bool. If set, emit DEFERRABLE or NOT DEFERRABLE when
|
||||
issuing DDL for this constraint.
|
||||
|
||||
:param initially:
|
||||
Optional string. If set, emit INITIALLY <value> when issuing DDL
|
||||
for this constraint.
|
||||
|
||||
:param using:
|
||||
Optional string. If set, emit USING <index_method> when issuing DDL
|
||||
for this constraint. Defaults to 'gist'.
|
||||
|
||||
:param where:
|
||||
Optional string. If set, emit WHERE <predicate> when issuing DDL
|
||||
for this constraint.
|
||||
|
||||
"""
|
||||
ColumnCollectionConstraint.__init__(
|
||||
self,
|
||||
*[col for col, op in elements],
|
||||
name=kw.get('name'),
|
||||
deferrable=kw.get('deferrable'),
|
||||
initially=kw.get('initially')
|
||||
)
|
||||
self.operators = {}
|
||||
for col_or_string, op in elements:
|
||||
name = getattr(col_or_string, 'name', col_or_string)
|
||||
self.operators[name] = op
|
||||
self.using = kw.get('using', 'gist')
|
||||
where = kw.get('where')
|
||||
if where:
|
||||
self.where = expression._literal_as_text(where)
|
||||
|
||||
def copy(self, **kw):
|
||||
elements = [(col, self.operators[col])
|
||||
for col in self.columns.keys()]
|
||||
c = self.__class__(*elements,
|
||||
name=self.name,
|
||||
deferrable=self.deferrable,
|
||||
initially=self.initially)
|
||||
c.dispatch._update(self.dispatch)
|
||||
return c
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
# postgresql/hstore.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
import re
|
||||
|
||||
from .base import ARRAY, ischema_names
|
||||
from ... import types as sqltypes
|
||||
from ...sql import functions as sqlfunc
|
||||
from ...sql.operators import custom_op
|
||||
from ... import util
|
||||
|
||||
__all__ = ('HSTORE', 'hstore')
|
||||
|
||||
# My best guess at the parsing rules of hstore literals, since no formal
|
||||
# grammar is given. This is mostly reverse engineered from PG's input parser
|
||||
# behavior.
|
||||
HSTORE_PAIR_RE = re.compile(r"""
|
||||
(
|
||||
"(?P<key> (\\ . | [^"])* )" # Quoted key
|
||||
)
|
||||
[ ]* => [ ]* # Pair operator, optional adjoining whitespace
|
||||
(
|
||||
(?P<value_null> NULL ) # NULL value
|
||||
| "(?P<value> (\\ . | [^"])* )" # Quoted value
|
||||
)
|
||||
""", re.VERBOSE)
|
||||
|
||||
HSTORE_DELIMITER_RE = re.compile(r"""
|
||||
[ ]* , [ ]*
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def _parse_error(hstore_str, pos):
|
||||
"""format an unmarshalling error."""
|
||||
|
||||
ctx = 20
|
||||
hslen = len(hstore_str)
|
||||
|
||||
parsed_tail = hstore_str[max(pos - ctx - 1, 0):min(pos, hslen)]
|
||||
residual = hstore_str[min(pos, hslen):min(pos + ctx + 1, hslen)]
|
||||
|
||||
if len(parsed_tail) > ctx:
|
||||
parsed_tail = '[...]' + parsed_tail[1:]
|
||||
if len(residual) > ctx:
|
||||
residual = residual[:-1] + '[...]'
|
||||
|
||||
return "After %r, could not parse residual at position %d: %r" % (
|
||||
parsed_tail, pos, residual)
|
||||
|
||||
|
||||
def _parse_hstore(hstore_str):
|
||||
"""Parse an hstore from it's literal string representation.
|
||||
|
||||
Attempts to approximate PG's hstore input parsing rules as closely as
|
||||
possible. Although currently this is not strictly necessary, since the
|
||||
current implementation of hstore's output syntax is stricter than what it
|
||||
accepts as input, the documentation makes no guarantees that will always
|
||||
be the case.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
result = {}
|
||||
pos = 0
|
||||
pair_match = HSTORE_PAIR_RE.match(hstore_str)
|
||||
|
||||
while pair_match is not None:
|
||||
key = pair_match.group('key').replace(r'\"', '"').replace("\\\\", "\\")
|
||||
if pair_match.group('value_null'):
|
||||
value = None
|
||||
else:
|
||||
value = pair_match.group('value').replace(r'\"', '"').replace("\\\\", "\\")
|
||||
result[key] = value
|
||||
|
||||
pos += pair_match.end()
|
||||
|
||||
delim_match = HSTORE_DELIMITER_RE.match(hstore_str[pos:])
|
||||
if delim_match is not None:
|
||||
pos += delim_match.end()
|
||||
|
||||
pair_match = HSTORE_PAIR_RE.match(hstore_str[pos:])
|
||||
|
||||
if pos != len(hstore_str):
|
||||
raise ValueError(_parse_error(hstore_str, pos))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _serialize_hstore(val):
|
||||
"""Serialize a dictionary into an hstore literal. Keys and values must
|
||||
both be strings (except None for values).
|
||||
|
||||
"""
|
||||
def esc(s, position):
|
||||
if position == 'value' and s is None:
|
||||
return 'NULL'
|
||||
elif isinstance(s, util.string_types):
|
||||
return '"%s"' % s.replace("\\", "\\\\").replace('"', r'\"')
|
||||
else:
|
||||
raise ValueError("%r in %s position is not a string." %
|
||||
(s, position))
|
||||
|
||||
return ', '.join('%s=>%s' % (esc(k, 'key'), esc(v, 'value'))
|
||||
for k, v in val.items())
|
||||
|
||||
|
||||
class HSTORE(sqltypes.Concatenable, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql HSTORE type.
|
||||
|
||||
The :class:`.HSTORE` type stores dictionaries containing strings, e.g.::
|
||||
|
||||
data_table = Table('data_table', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', HSTORE)
|
||||
)
|
||||
|
||||
with engine.connect() as conn:
|
||||
conn.execute(
|
||||
data_table.insert(),
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
)
|
||||
|
||||
:class:`.HSTORE` provides for a wide range of operations, including:
|
||||
|
||||
* Index operations::
|
||||
|
||||
data_table.c.data['some key'] == 'some value'
|
||||
|
||||
* Containment operations::
|
||||
|
||||
data_table.c.data.has_key('some key')
|
||||
|
||||
data_table.c.data.has_all(['one', 'two', 'three'])
|
||||
|
||||
* Concatenation::
|
||||
|
||||
data_table.c.data + {"k1": "v1"}
|
||||
|
||||
For a full list of special methods see :class:`.HSTORE.comparator_factory`.
|
||||
|
||||
For usage with the SQLAlchemy ORM, it may be desirable to combine
|
||||
the usage of :class:`.HSTORE` with :class:`.MutableDict` dictionary
|
||||
now part of the :mod:`sqlalchemy.ext.mutable`
|
||||
extension. This extension will allow "in-place" changes to the
|
||||
dictionary, e.g. addition of new keys or replacement/removal of existing
|
||||
keys to/from the current dictionary, to produce events which will be detected
|
||||
by the unit of work::
|
||||
|
||||
from sqlalchemy.ext.mutable import MutableDict
|
||||
|
||||
class MyClass(Base):
|
||||
__tablename__ = 'data_table'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
data = Column(MutableDict.as_mutable(HSTORE))
|
||||
|
||||
my_object = session.query(MyClass).one()
|
||||
|
||||
# in-place mutation, requires Mutable extension
|
||||
# in order for the ORM to detect
|
||||
my_object.data['some_key'] = 'some value'
|
||||
|
||||
session.commit()
|
||||
|
||||
When the :mod:`sqlalchemy.ext.mutable` extension is not used, the ORM
|
||||
will not be alerted to any changes to the contents of an existing dictionary,
|
||||
unless that dictionary value is re-assigned to the HSTORE-attribute itself,
|
||||
thus generating a change event.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.hstore` - render the Postgresql ``hstore()`` function.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'HSTORE'
|
||||
|
||||
class comparator_factory(sqltypes.Concatenable.Comparator):
|
||||
"""Define comparison operations for :class:`.HSTORE`."""
|
||||
|
||||
def has_key(self, other):
|
||||
"""Boolean expression. Test for presence of a key. Note that the
|
||||
key may be a SQLA expression.
|
||||
"""
|
||||
return self.expr.op('?')(other)
|
||||
|
||||
def has_all(self, other):
|
||||
"""Boolean expression. Test for presence of all keys in the PG
|
||||
array.
|
||||
"""
|
||||
return self.expr.op('?&')(other)
|
||||
|
||||
def has_any(self, other):
|
||||
"""Boolean expression. Test for presence of any key in the PG
|
||||
array.
|
||||
"""
|
||||
return self.expr.op('?|')(other)
|
||||
|
||||
def defined(self, key):
|
||||
"""Boolean expression. Test for presence of a non-NULL value for
|
||||
the key. Note that the key may be a SQLA expression.
|
||||
"""
|
||||
return _HStoreDefinedFunction(self.expr, key)
|
||||
|
||||
def contains(self, other, **kwargs):
|
||||
"""Boolean expression. Test if keys are a superset of the keys of
|
||||
the argument hstore expression.
|
||||
"""
|
||||
return self.expr.op('@>')(other)
|
||||
|
||||
def contained_by(self, other):
|
||||
"""Boolean expression. Test if keys are a proper subset of the
|
||||
keys of the argument hstore expression.
|
||||
"""
|
||||
return self.expr.op('<@')(other)
|
||||
|
||||
def __getitem__(self, other):
|
||||
"""Text expression. Get the value at a given key. Note that the
|
||||
key may be a SQLA expression.
|
||||
"""
|
||||
return self.expr.op('->', precedence=5)(other)
|
||||
|
||||
def delete(self, key):
|
||||
"""HStore expression. Returns the contents of this hstore with the
|
||||
given key deleted. Note that the key may be a SQLA expression.
|
||||
"""
|
||||
if isinstance(key, dict):
|
||||
key = _serialize_hstore(key)
|
||||
return _HStoreDeleteFunction(self.expr, key)
|
||||
|
||||
def slice(self, array):
|
||||
"""HStore expression. Returns a subset of an hstore defined by
|
||||
array of keys.
|
||||
"""
|
||||
return _HStoreSliceFunction(self.expr, array)
|
||||
|
||||
def keys(self):
|
||||
"""Text array expression. Returns array of keys."""
|
||||
return _HStoreKeysFunction(self.expr)
|
||||
|
||||
def vals(self):
|
||||
"""Text array expression. Returns array of values."""
|
||||
return _HStoreValsFunction(self.expr)
|
||||
|
||||
def array(self):
|
||||
"""Text array expression. Returns array of alternating keys and
|
||||
values.
|
||||
"""
|
||||
return _HStoreArrayFunction(self.expr)
|
||||
|
||||
def matrix(self):
|
||||
"""Text array expression. Returns array of [key, value] pairs."""
|
||||
return _HStoreMatrixFunction(self.expr)
|
||||
|
||||
def _adapt_expression(self, op, other_comparator):
|
||||
if isinstance(op, custom_op):
|
||||
if op.opstring in ['?', '?&', '?|', '@>', '<@']:
|
||||
return op, sqltypes.Boolean
|
||||
elif op.opstring == '->':
|
||||
return op, sqltypes.Text
|
||||
return sqltypes.Concatenable.Comparator.\
|
||||
_adapt_expression(self, op, other_comparator)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
if util.py2k:
|
||||
encoding = dialect.encoding
|
||||
def process(value):
|
||||
if isinstance(value, dict):
|
||||
return _serialize_hstore(value).encode(encoding)
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
def process(value):
|
||||
if isinstance(value, dict):
|
||||
return _serialize_hstore(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if util.py2k:
|
||||
encoding = dialect.encoding
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return _parse_hstore(value.decode(encoding))
|
||||
else:
|
||||
return value
|
||||
else:
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return _parse_hstore(value)
|
||||
else:
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
ischema_names['hstore'] = HSTORE
|
||||
|
||||
|
||||
class hstore(sqlfunc.GenericFunction):
|
||||
"""Construct an hstore value within a SQL expression using the
|
||||
Postgresql ``hstore()`` function.
|
||||
|
||||
The :class:`.hstore` function accepts one or two arguments as described
|
||||
in the Postgresql documentation.
|
||||
|
||||
E.g.::
|
||||
|
||||
from sqlalchemy.dialects.postgresql import array, hstore
|
||||
|
||||
select([hstore('key1', 'value1')])
|
||||
|
||||
select([
|
||||
hstore(
|
||||
array(['key1', 'key2', 'key3']),
|
||||
array(['value1', 'value2', 'value3'])
|
||||
)
|
||||
])
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
.. seealso::
|
||||
|
||||
:class:`.HSTORE` - the Postgresql ``HSTORE`` datatype.
|
||||
|
||||
"""
|
||||
type = HSTORE
|
||||
name = 'hstore'
|
||||
|
||||
|
||||
class _HStoreDefinedFunction(sqlfunc.GenericFunction):
|
||||
type = sqltypes.Boolean
|
||||
name = 'defined'
|
||||
|
||||
|
||||
class _HStoreDeleteFunction(sqlfunc.GenericFunction):
|
||||
type = HSTORE
|
||||
name = 'delete'
|
||||
|
||||
|
||||
class _HStoreSliceFunction(sqlfunc.GenericFunction):
|
||||
type = HSTORE
|
||||
name = 'slice'
|
||||
|
||||
|
||||
class _HStoreKeysFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'akeys'
|
||||
|
||||
|
||||
class _HStoreValsFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'avals'
|
||||
|
||||
|
||||
class _HStoreArrayFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'hstore_to_array'
|
||||
|
||||
|
||||
class _HStoreMatrixFunction(sqlfunc.GenericFunction):
|
||||
type = ARRAY(sqltypes.Text)
|
||||
name = 'hstore_to_matrix'
|
||||
@@ -0,0 +1,199 @@
|
||||
# postgresql/json.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
|
||||
from .base import ischema_names
|
||||
from ... import types as sqltypes
|
||||
from ...sql.operators import custom_op
|
||||
from ... import sql
|
||||
from ...sql import elements
|
||||
from ... import util
|
||||
|
||||
__all__ = ('JSON', 'JSONElement')
|
||||
|
||||
|
||||
class JSONElement(elements.BinaryExpression):
|
||||
"""Represents accessing an element of a :class:`.JSON` value.
|
||||
|
||||
The :class:`.JSONElement` is produced whenever using the Python index
|
||||
operator on an expression that has the type :class:`.JSON`::
|
||||
|
||||
expr = mytable.c.json_data['some_key']
|
||||
|
||||
The expression typically compiles to a JSON access such as ``col -> key``.
|
||||
Modifiers are then available for typing behavior, including :meth:`.JSONElement.cast`
|
||||
and :attr:`.JSONElement.astext`.
|
||||
|
||||
"""
|
||||
def __init__(self, left, right, astext=False, opstring=None, result_type=None):
|
||||
self._astext = astext
|
||||
if opstring is None:
|
||||
if hasattr(right, '__iter__') and \
|
||||
not isinstance(right, util.string_types):
|
||||
opstring = "#>"
|
||||
right = "{%s}" % (", ".join(util.text_type(elem) for elem in right))
|
||||
else:
|
||||
opstring = "->"
|
||||
|
||||
self._json_opstring = opstring
|
||||
operator = custom_op(opstring, precedence=5)
|
||||
right = left._check_literal(left, operator, right)
|
||||
super(JSONElement, self).__init__(left, right, operator, type_=result_type)
|
||||
|
||||
@property
|
||||
def astext(self):
|
||||
"""Convert this :class:`.JSONElement` to use the 'astext' operator
|
||||
when evaluated.
|
||||
|
||||
E.g.::
|
||||
|
||||
select([data_table.c.data['some key'].astext])
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.JSONElement.cast`
|
||||
|
||||
"""
|
||||
if self._astext:
|
||||
return self
|
||||
else:
|
||||
return JSONElement(
|
||||
self.left,
|
||||
self.right,
|
||||
astext=True,
|
||||
opstring=self._json_opstring + ">",
|
||||
result_type=sqltypes.String(convert_unicode=True)
|
||||
)
|
||||
|
||||
def cast(self, type_):
|
||||
"""Convert this :class:`.JSONElement` to apply both the 'astext' operator
|
||||
as well as an explicit type cast when evaulated.
|
||||
|
||||
E.g.::
|
||||
|
||||
select([data_table.c.data['some key'].cast(Integer)])
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.JSONElement.astext`
|
||||
|
||||
"""
|
||||
if not self._astext:
|
||||
return self.astext.cast(type_)
|
||||
else:
|
||||
return sql.cast(self, type_)
|
||||
|
||||
|
||||
class JSON(sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql JSON type.
|
||||
|
||||
The :class:`.JSON` type stores arbitrary JSON format data, e.g.::
|
||||
|
||||
data_table = Table('data_table', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('data', JSON)
|
||||
)
|
||||
|
||||
with engine.connect() as conn:
|
||||
conn.execute(
|
||||
data_table.insert(),
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
)
|
||||
|
||||
:class:`.JSON` provides several operations:
|
||||
|
||||
* Index operations::
|
||||
|
||||
data_table.c.data['some key']
|
||||
|
||||
* Index operations returning text (required for text comparison)::
|
||||
|
||||
data_table.c.data['some key'].astext == 'some value'
|
||||
|
||||
* Index operations with a built-in CAST call::
|
||||
|
||||
data_table.c.data['some key'].cast(Integer) == 5
|
||||
|
||||
* Path index operations::
|
||||
|
||||
data_table.c.data[('key_1', 'key_2', ..., 'key_n')]
|
||||
|
||||
* Path index operations returning text (required for text comparison)::
|
||||
|
||||
data_table.c.data[('key_1', 'key_2', ..., 'key_n')].astext == 'some value'
|
||||
|
||||
Index operations return an instance of :class:`.JSONElement`, which represents
|
||||
an expression such as ``column -> index``. This element then defines
|
||||
methods such as :attr:`.JSONElement.astext` and :meth:`.JSONElement.cast`
|
||||
for setting up type behavior.
|
||||
|
||||
The :class:`.JSON` type, when used with the SQLAlchemy ORM, does not detect
|
||||
in-place mutations to the structure. In order to detect these, the
|
||||
:mod:`sqlalchemy.ext.mutable` extension must be used. This extension will
|
||||
allow "in-place" changes to the datastructure to produce events which
|
||||
will be detected by the unit of work. See the example at :class:`.HSTORE`
|
||||
for a simple example involving a dictionary.
|
||||
|
||||
Custom serializers and deserializers are specified at the dialect level,
|
||||
that is using :func:`.create_engine`. The reason for this is that when
|
||||
using psycopg2, the DBAPI only allows serializers at the per-cursor
|
||||
or per-connection level. E.g.::
|
||||
|
||||
engine = create_engine("postgresql://scott:tiger@localhost/test",
|
||||
json_serializer=my_serialize_fn,
|
||||
json_deserializer=my_deserialize_fn
|
||||
)
|
||||
|
||||
When using the psycopg2 dialect, the json_deserializer is registered
|
||||
against the database using ``psycopg2.extras.register_default_json``.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'JSON'
|
||||
|
||||
class comparator_factory(sqltypes.Concatenable.Comparator):
|
||||
"""Define comparison operations for :class:`.JSON`."""
|
||||
|
||||
def __getitem__(self, other):
|
||||
"""Get the value at a given key."""
|
||||
|
||||
return JSONElement(self.expr, other)
|
||||
|
||||
def _adapt_expression(self, op, other_comparator):
|
||||
if isinstance(op, custom_op):
|
||||
if op.opstring == '->':
|
||||
return op, sqltypes.Text
|
||||
return sqltypes.Concatenable.Comparator.\
|
||||
_adapt_expression(self, op, other_comparator)
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
json_serializer = dialect._json_serializer or json.dumps
|
||||
if util.py2k:
|
||||
encoding = dialect.encoding
|
||||
def process(value):
|
||||
return json_serializer(value).encode(encoding)
|
||||
else:
|
||||
def process(value):
|
||||
return json_serializer(value)
|
||||
return process
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
json_deserializer = dialect._json_deserializer or json.loads
|
||||
if util.py2k:
|
||||
encoding = dialect.encoding
|
||||
def process(value):
|
||||
return json_deserializer(value.decode(encoding))
|
||||
else:
|
||||
def process(value):
|
||||
return json_deserializer(value)
|
||||
return process
|
||||
|
||||
|
||||
ischema_names['json'] = JSON
|
||||
@@ -1,16 +1,15 @@
|
||||
# postgresql/pg8000.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the PostgreSQL database via the pg8000 driver.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
URLs are of the form
|
||||
``postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...]``.
|
||||
"""
|
||||
.. dialect:: postgresql+pg8000
|
||||
:name: pg8000
|
||||
:dbapi: pg8000
|
||||
:connectstring: postgresql+pg8000://user:password@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://pybrary.net/pg8000/
|
||||
|
||||
Unicode
|
||||
-------
|
||||
@@ -27,19 +26,22 @@ Passing data from/to the Interval type is not supported as of
|
||||
yet.
|
||||
|
||||
"""
|
||||
from sqlalchemy import util, exc
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from sqlalchemy import processors
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, \
|
||||
from ... import util, exc
|
||||
import decimal
|
||||
from ... import processors
|
||||
from ... import types as sqltypes
|
||||
from .base import PGDialect, \
|
||||
PGCompiler, PGIdentifierPreparer, PGExecutionContext,\
|
||||
_DECIMAL_TYPES, _FLOAT_TYPES, _INT_TYPES
|
||||
|
||||
|
||||
class _PGNumeric(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self.asdecimal:
|
||||
if coltype in _FLOAT_TYPES:
|
||||
return processors.to_decimal_processor_factory(decimal.Decimal)
|
||||
return processors.to_decimal_processor_factory(
|
||||
decimal.Decimal,
|
||||
self._effective_decimal_return_scale)
|
||||
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
|
||||
# pg8000 returns Decimal natively for 1700
|
||||
return None
|
||||
@@ -61,17 +63,20 @@ class _PGNumericNoBind(_PGNumeric):
|
||||
def bind_processor(self, dialect):
|
||||
return None
|
||||
|
||||
|
||||
class PGExecutionContext_pg8000(PGExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class PGCompiler_pg8000(PGCompiler):
|
||||
def visit_mod(self, binary, **kw):
|
||||
return self.process(binary.left) + " %% " + self.process(binary.right)
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
if '%%' in text:
|
||||
util.warn("The SQLAlchemy postgresql dialect now automatically escapes '%' in text() "
|
||||
util.warn("The SQLAlchemy postgresql dialect "
|
||||
"now automatically escapes '%' in text() "
|
||||
"expressions to '%%'.")
|
||||
return text.replace('%', '%%')
|
||||
|
||||
@@ -99,8 +104,8 @@ class PGDialect_pg8000(PGDialect):
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric : _PGNumericNoBind,
|
||||
sqltypes.Float : _PGNumeric
|
||||
sqltypes.Numeric: _PGNumericNoBind,
|
||||
sqltypes.Float: _PGNumeric
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,42 +1,39 @@
|
||||
# postgresql/psycopg2.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the PostgreSQL database via the psycopg2 driver.
|
||||
"""
|
||||
.. dialect:: postgresql+psycopg2
|
||||
:name: psycopg2
|
||||
:dbapi: psycopg2
|
||||
:connectstring: postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://pypi.python.org/pypi/psycopg2/
|
||||
|
||||
Driver
|
||||
------
|
||||
|
||||
The psycopg2 driver is 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
|
||||
``postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]``.
|
||||
psycopg2 Connect Arguments
|
||||
-----------------------------------
|
||||
|
||||
psycopg2-specific keyword arguments which are accepted by
|
||||
:func:`.create_engine()` are:
|
||||
|
||||
* *server_side_cursors* - Enable the usage of "server side cursors" for SQL
|
||||
* ``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
|
||||
:class:`~sqlalchemy.engine.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.
|
||||
Note that the ``stream_results=True`` execution option is a more targeted
|
||||
way of enabling this mode on a per-execution basis.
|
||||
* *use_native_unicode* - Enable the usage of Psycopg2 "native unicode" mode
|
||||
per connection. True by default.
|
||||
* ``use_native_unicode``: Enable the usage of Psycopg2 "native unicode" mode
|
||||
per connection. True by default.
|
||||
* ``isolation_level``: This option, available for all Posgtresql dialects,
|
||||
includes the ``AUTOCOMMIT`` isolation level when using the psycopg2
|
||||
dialect. See :ref:`psycopg2_isolation_level`.
|
||||
|
||||
|
||||
Unix Domain Connections
|
||||
------------------------
|
||||
@@ -66,11 +63,14 @@ The following DBAPI-specific options are respected when used with
|
||||
:meth:`.Query.execution_options`, in addition to those not specific to DBAPIs:
|
||||
|
||||
* isolation_level - Set the transaction isolation level for the lifespan of a
|
||||
:class:`.Connection` (can only be set on a connection, not a statement or query).
|
||||
This includes the options ``SERIALIZABLE``, ``READ COMMITTED``,
|
||||
``READ UNCOMMITTED`` and ``REPEATABLE READ``.
|
||||
* stream_results - Enable or disable usage of server side cursors.
|
||||
If ``None`` or not set, the ``server_side_cursors`` option of the :class:`.Engine` is used.
|
||||
:class:`.Connection` (can only be set on a connection, not a statement
|
||||
or query). See :ref:`psycopg2_isolation_level`.
|
||||
|
||||
* stream_results - Enable or disable usage of psycopg2 server side cursors -
|
||||
this feature makes use of "named" cursors in combination with special
|
||||
result handling methods so that result rows are not fully buffered.
|
||||
If ``None`` or not set, the ``server_side_cursors`` option of the
|
||||
:class:`.Engine` is used.
|
||||
|
||||
Unicode
|
||||
-------
|
||||
@@ -98,13 +98,14 @@ on all new connections based on the value passed to
|
||||
This overrides the encoding specified in the Postgresql client configuration.
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
The psycopg2-specific ``client_encoding`` parameter to :func:`.create_engine`.
|
||||
The psycopg2-specific ``client_encoding`` parameter to
|
||||
:func:`.create_engine`.
|
||||
|
||||
SQLAlchemy can also be instructed to skip the usage of the psycopg2
|
||||
``UNICODE`` extension and to instead utilize it's own unicode encode/decode
|
||||
services, which are normally reserved only for those DBAPIs that don't
|
||||
fully support unicode directly. Passing ``use_native_unicode=False``
|
||||
to :func:`.create_engine` will disable usage of ``psycopg2.extensions.UNICODE``.
|
||||
fully support unicode directly. Passing ``use_native_unicode=False`` to
|
||||
:func:`.create_engine` will disable usage of ``psycopg2.extensions.UNICODE``.
|
||||
SQLAlchemy will instead encode data itself into Python bytestrings on the way
|
||||
in and coerce from bytes on the way back,
|
||||
using the value of the :func:`.create_engine` ``encoding`` parameter, which
|
||||
@@ -118,16 +119,31 @@ Transactions
|
||||
|
||||
The psycopg2 dialect fully supports SAVEPOINT and two-phase commit operations.
|
||||
|
||||
.. _psycopg2_isolation:
|
||||
.. _psycopg2_isolation_level:
|
||||
|
||||
Transaction Isolation Level
|
||||
---------------------------
|
||||
Psycopg2 Transaction Isolation Level
|
||||
-------------------------------------
|
||||
|
||||
The ``isolation_level`` parameter of :func:`.create_engine` here makes use
|
||||
As discussed in :ref:`postgresql_isolation_level`,
|
||||
all Postgresql dialects support setting of transaction isolation level
|
||||
both via the ``isolation_level`` parameter passed to :func:`.create_engine`,
|
||||
as well as the ``isolation_level`` argument used by :meth:`.Connection.execution_options`.
|
||||
When using the psycopg2 dialect, these options make use of
|
||||
psycopg2's ``set_isolation_level()`` connection method, rather than
|
||||
issuing a ``SET SESSION CHARACTERISTICS`` command. This because psycopg2
|
||||
resets the isolation level on each new transaction, and needs to know
|
||||
at the API level what level should be used.
|
||||
emitting a Postgresql directive; this is because psycopg2's API-level
|
||||
setting is always emitted at the start of each transaction in any case.
|
||||
|
||||
The psycopg2 dialect supports these constants for isolation level:
|
||||
|
||||
* ``READ COMMITTED``
|
||||
* ``READ UNCOMMITTED``
|
||||
* ``REPEATABLE READ``
|
||||
* ``SERIALIZABLE``
|
||||
* ``AUTOCOMMIT``
|
||||
|
||||
.. versionadded:: 0.8.2 support for AUTOCOMMIT isolation level when using
|
||||
psycopg2.
|
||||
|
||||
|
||||
NOTICE logging
|
||||
---------------
|
||||
@@ -138,22 +154,32 @@ The psycopg2 dialect will log Postgresql NOTICE messages via the
|
||||
import logging
|
||||
logging.getLogger('sqlalchemy.dialects.postgresql').setLevel(logging.INFO)
|
||||
|
||||
HSTORE type
|
||||
------------
|
||||
|
||||
The psycopg2 dialect will make use of the
|
||||
``psycopg2.extensions.register_hstore()`` extension when using the HSTORE
|
||||
type. This replaces SQLAlchemy's pure-Python HSTORE coercion which takes
|
||||
effect for other DBAPIs.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
import logging
|
||||
|
||||
from sqlalchemy import util, exc
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from sqlalchemy import processors
|
||||
from sqlalchemy.engine import base
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, PGCompiler, \
|
||||
from ... import util, exc
|
||||
import decimal
|
||||
from ... import processors
|
||||
from ...engine import result as _result
|
||||
from ...sql import expression
|
||||
from ... import types as sqltypes
|
||||
from .base import PGDialect, PGCompiler, \
|
||||
PGIdentifierPreparer, PGExecutionContext, \
|
||||
ENUM, ARRAY, _DECIMAL_TYPES, _FLOAT_TYPES,\
|
||||
_INT_TYPES
|
||||
from .hstore import HSTORE
|
||||
from .json import JSON
|
||||
|
||||
|
||||
logger = logging.getLogger('sqlalchemy.dialects.postgresql')
|
||||
@@ -166,7 +192,9 @@ class _PGNumeric(sqltypes.Numeric):
|
||||
def result_processor(self, dialect, coltype):
|
||||
if self.asdecimal:
|
||||
if coltype in _FLOAT_TYPES:
|
||||
return processors.to_decimal_processor_factory(decimal.Decimal)
|
||||
return processors.to_decimal_processor_factory(
|
||||
decimal.Decimal,
|
||||
self._effective_decimal_return_scale)
|
||||
elif coltype in _DECIMAL_TYPES or coltype in _INT_TYPES:
|
||||
# pg8000 returns Decimal natively for 1700
|
||||
return None
|
||||
@@ -183,24 +211,37 @@ class _PGNumeric(sqltypes.Numeric):
|
||||
raise exc.InvalidRequestError(
|
||||
"Unknown PG numeric type: %d" % coltype)
|
||||
|
||||
class _PGEnum(ENUM):
|
||||
def __init__(self, *arg, **kw):
|
||||
super(_PGEnum, self).__init__(*arg, **kw)
|
||||
# Py2K
|
||||
if self.convert_unicode:
|
||||
self.convert_unicode = "force"
|
||||
# end Py2K
|
||||
|
||||
class _PGArray(ARRAY):
|
||||
def __init__(self, *arg, **kw):
|
||||
super(_PGArray, self).__init__(*arg, **kw)
|
||||
# Py2K
|
||||
# FIXME: this check won't work for setups that
|
||||
# have convert_unicode only on their create_engine().
|
||||
if isinstance(self.item_type, sqltypes.String) and \
|
||||
self.item_type.convert_unicode:
|
||||
self.item_type.convert_unicode = "force"
|
||||
# end Py2K
|
||||
class _PGEnum(ENUM):
|
||||
def result_processor(self, dialect, coltype):
|
||||
if util.py2k and self.convert_unicode is True:
|
||||
# we can't easily use PG's extensions here because
|
||||
# the OID is on the fly, and we need to give it a python
|
||||
# function anyway - not really worth it.
|
||||
self.convert_unicode = "force_nocheck"
|
||||
return super(_PGEnum, self).result_processor(dialect, coltype)
|
||||
|
||||
class _PGHStore(HSTORE):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect._has_native_hstore:
|
||||
return None
|
||||
else:
|
||||
return super(_PGHStore, self).bind_processor(dialect)
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if dialect._has_native_hstore:
|
||||
return None
|
||||
else:
|
||||
return super(_PGHStore, self).result_processor(dialect, coltype)
|
||||
|
||||
|
||||
class _PGJSON(JSON):
|
||||
|
||||
def result_processor(self, dialect, coltype):
|
||||
if dialect._has_native_json:
|
||||
return None
|
||||
else:
|
||||
return super(_PGJSON, self).result_processor(dialect, coltype)
|
||||
|
||||
# When we're handed literal SQL, ensure it's a SELECT-query. Since
|
||||
# 8.3, combining cursors and "FOR UPDATE" has been fine.
|
||||
@@ -210,6 +251,7 @@ SERVER_SIDE_CURSOR_RE = re.compile(
|
||||
|
||||
_server_side_id = util.counter()
|
||||
|
||||
|
||||
class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
def create_cursor(self):
|
||||
# TODO: coverage for server side cursors + select.for_update()
|
||||
@@ -221,12 +263,13 @@ class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
or \
|
||||
(
|
||||
(not self.compiled or
|
||||
isinstance(self.compiled.statement, expression._TextClause))
|
||||
isinstance(self.compiled.statement, expression.TextClause))
|
||||
and self.statement and SERVER_SIDE_CURSOR_RE.match(self.statement))
|
||||
)
|
||||
)
|
||||
else:
|
||||
is_server_side = self.execution_options.get('stream_results', False)
|
||||
is_server_side = \
|
||||
self.execution_options.get('stream_results', False)
|
||||
|
||||
self.__is_server_side = is_server_side
|
||||
if is_server_side:
|
||||
@@ -243,9 +286,9 @@ class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
self._log_notices(self.cursor)
|
||||
|
||||
if self.__is_server_side:
|
||||
return base.BufferedRowResultProxy(self)
|
||||
return _result.BufferedRowResultProxy(self)
|
||||
else:
|
||||
return base.ResultProxy(self)
|
||||
return _result.ResultProxy(self)
|
||||
|
||||
def _log_notices(self, cursor):
|
||||
for notice in cursor.connection.notices:
|
||||
@@ -257,8 +300,9 @@ class PGExecutionContext_psycopg2(PGExecutionContext):
|
||||
|
||||
|
||||
class PGCompiler_psycopg2(PGCompiler):
|
||||
def visit_mod(self, binary, **kw):
|
||||
return self.process(binary.left) + " %% " + self.process(binary.right)
|
||||
def visit_mod_binary(self, binary, operator, **kw):
|
||||
return self.process(binary.left, **kw) + " %% " + \
|
||||
self.process(binary.right, **kw)
|
||||
|
||||
def post_process_text(self, text):
|
||||
return text.replace('%', '%%')
|
||||
@@ -269,11 +313,12 @@ class PGIdentifierPreparer_psycopg2(PGIdentifierPreparer):
|
||||
value = value.replace(self.escape_quote, self.escape_to_quote)
|
||||
return value.replace('%', '%%')
|
||||
|
||||
|
||||
class PGDialect_psycopg2(PGDialect):
|
||||
driver = 'psycopg2'
|
||||
# Py2K
|
||||
supports_unicode_statements = False
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
supports_unicode_statements = False
|
||||
|
||||
default_paramstyle = 'pyformat'
|
||||
supports_sane_multi_rowcount = False
|
||||
execution_ctx_cls = PGExecutionContext_psycopg2
|
||||
@@ -281,21 +326,28 @@ class PGDialect_psycopg2(PGDialect):
|
||||
preparer = PGIdentifierPreparer_psycopg2
|
||||
psycopg2_version = (0, 0)
|
||||
|
||||
_has_native_hstore = False
|
||||
_has_native_json = False
|
||||
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric : _PGNumeric,
|
||||
ENUM : _PGEnum, # needs force_unicode
|
||||
sqltypes.Enum : _PGEnum, # needs force_unicode
|
||||
ARRAY : _PGArray, # needs force_unicode
|
||||
sqltypes.Numeric: _PGNumeric,
|
||||
ENUM: _PGEnum, # needs force_unicode
|
||||
sqltypes.Enum: _PGEnum, # needs force_unicode
|
||||
HSTORE: _PGHStore,
|
||||
JSON: _PGJSON
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, server_side_cursors=False, use_native_unicode=True,
|
||||
client_encoding=None, **kwargs):
|
||||
client_encoding=None,
|
||||
use_native_hstore=True,
|
||||
**kwargs):
|
||||
PGDialect.__init__(self, **kwargs)
|
||||
self.server_side_cursors = server_side_cursors
|
||||
self.use_native_unicode = use_native_unicode
|
||||
self.use_native_hstore = use_native_hstore
|
||||
self.supports_unicode_binds = use_native_unicode
|
||||
self.client_encoding = client_encoding
|
||||
if self.dbapi and hasattr(self.dbapi, '__version__'):
|
||||
@@ -307,19 +359,27 @@ class PGDialect_psycopg2(PGDialect):
|
||||
for x in m.group(1, 2, 3)
|
||||
if x is not None)
|
||||
|
||||
def initialize(self, connection):
|
||||
super(PGDialect_psycopg2, self).initialize(connection)
|
||||
self._has_native_hstore = self.use_native_hstore and \
|
||||
self._hstore_oids(connection.connection) \
|
||||
is not None
|
||||
self._has_native_json = self.psycopg2_version >= (2, 5)
|
||||
|
||||
@classmethod
|
||||
def dbapi(cls):
|
||||
psycopg = __import__('psycopg2')
|
||||
return psycopg
|
||||
import psycopg2
|
||||
return psycopg2
|
||||
|
||||
@util.memoized_property
|
||||
def _isolation_lookup(self):
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
from psycopg2 import extensions
|
||||
return {
|
||||
'READ COMMITTED':extensions.ISOLATION_LEVEL_READ_COMMITTED,
|
||||
'READ UNCOMMITTED':extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
|
||||
'REPEATABLE READ':extensions.ISOLATION_LEVEL_REPEATABLE_READ,
|
||||
'SERIALIZABLE':extensions.ISOLATION_LEVEL_SERIALIZABLE
|
||||
'AUTOCOMMIT': extensions.ISOLATION_LEVEL_AUTOCOMMIT,
|
||||
'READ COMMITTED': extensions.ISOLATION_LEVEL_READ_COMMITTED,
|
||||
'READ UNCOMMITTED': extensions.ISOLATION_LEVEL_READ_UNCOMMITTED,
|
||||
'REPEATABLE READ': extensions.ISOLATION_LEVEL_REPEATABLE_READ,
|
||||
'SERIALIZABLE': extensions.ISOLATION_LEVEL_SERIALIZABLE
|
||||
}
|
||||
|
||||
def set_isolation_level(self, connection, level):
|
||||
@@ -335,6 +395,8 @@ class PGDialect_psycopg2(PGDialect):
|
||||
connection.set_isolation_level(level)
|
||||
|
||||
def on_connect(self):
|
||||
from psycopg2 import extras, extensions
|
||||
|
||||
fns = []
|
||||
if self.client_encoding is not None:
|
||||
def on_connect(conn):
|
||||
@@ -347,9 +409,28 @@ class PGDialect_psycopg2(PGDialect):
|
||||
fns.append(on_connect)
|
||||
|
||||
if self.dbapi and self.use_native_unicode:
|
||||
extensions = __import__('psycopg2.extensions').extensions
|
||||
def on_connect(conn):
|
||||
extensions.register_type(extensions.UNICODE, conn)
|
||||
extensions.register_type(extensions.UNICODEARRAY, conn)
|
||||
fns.append(on_connect)
|
||||
|
||||
if self.dbapi and self.use_native_hstore:
|
||||
def on_connect(conn):
|
||||
hstore_oids = self._hstore_oids(conn)
|
||||
if hstore_oids is not None:
|
||||
oid, array_oid = hstore_oids
|
||||
if util.py2k:
|
||||
extras.register_hstore(conn, oid=oid,
|
||||
array_oid=array_oid,
|
||||
unicode=True)
|
||||
else:
|
||||
extras.register_hstore(conn, oid=oid,
|
||||
array_oid=array_oid)
|
||||
fns.append(on_connect)
|
||||
|
||||
if self.dbapi and self._json_deserializer:
|
||||
def on_connect(conn):
|
||||
extras.register_default_json(conn, loads=self._json_deserializer)
|
||||
fns.append(on_connect)
|
||||
|
||||
if fns:
|
||||
@@ -360,6 +441,15 @@ class PGDialect_psycopg2(PGDialect):
|
||||
else:
|
||||
return None
|
||||
|
||||
@util.memoized_instancemethod
|
||||
def _hstore_oids(self, conn):
|
||||
if self.psycopg2_version >= (2, 4):
|
||||
from psycopg2 import extras
|
||||
oids = extras.HstoreAdapter.get_oids(conn)
|
||||
if oids is not None and oids[0]:
|
||||
return oids[0:2]
|
||||
return None
|
||||
|
||||
def create_connect_args(self, url):
|
||||
opts = url.translate_connect_args(username='user')
|
||||
if 'port' in opts:
|
||||
@@ -368,24 +458,27 @@ class PGDialect_psycopg2(PGDialect):
|
||||
return ([], opts)
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
if isinstance(e, self.dbapi.OperationalError):
|
||||
# these error messages from libpq: interfaces/libpq/fe-misc.c.
|
||||
# TODO: these are sent through gettext in libpq and we can't
|
||||
# check within other locales - consider using connection.closed
|
||||
return 'terminating connection' in str(e) or \
|
||||
'closed the connection' in str(e) or \
|
||||
'connection not open' in str(e) or \
|
||||
'could not receive data from server' in str(e)
|
||||
elif isinstance(e, self.dbapi.InterfaceError):
|
||||
# psycopg2 client errors, psycopg2/conenction.h, psycopg2/cursor.h
|
||||
return 'connection already closed' in str(e) or \
|
||||
'cursor already closed' in str(e)
|
||||
elif isinstance(e, self.dbapi.ProgrammingError):
|
||||
# not sure where this path is originally from, it may
|
||||
# be obsolete. It really says "losed", not "closed".
|
||||
return "losed the connection unexpectedly" in str(e)
|
||||
else:
|
||||
return False
|
||||
if isinstance(e, self.dbapi.Error):
|
||||
str_e = str(e).partition("\n")[0]
|
||||
for msg in [
|
||||
# these error messages from libpq: interfaces/libpq/fe-misc.c
|
||||
# and interfaces/libpq/fe-secure.c.
|
||||
# TODO: these are sent through gettext in libpq and we can't
|
||||
# check within other locales - consider using connection.closed
|
||||
'terminating connection',
|
||||
'closed the connection',
|
||||
'connection not open',
|
||||
'could not receive data from server',
|
||||
# psycopg2 client errors, psycopg2/conenction.h, psycopg2/cursor.h
|
||||
'connection already closed',
|
||||
'cursor already closed',
|
||||
# not sure where this path is originally from, it may
|
||||
# be obsolete. It really says "losed", not "closed".
|
||||
'losed the connection unexpectedly'
|
||||
]:
|
||||
idx = str_e.find(msg)
|
||||
if idx >= 0 and '"' not in str_e[:idx]:
|
||||
return True
|
||||
return False
|
||||
|
||||
dialect = PGDialect_psycopg2
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
# postgresql/pypostgresql.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the PostgreSQL database via py-postgresql.
|
||||
|
||||
Connecting
|
||||
----------
|
||||
|
||||
URLs are of the form ``postgresql+pypostgresql://user:password@host:port/dbname[?key=value&key=value...]``.
|
||||
"""
|
||||
.. dialect:: postgresql+pypostgresql
|
||||
:name: py-postgresql
|
||||
:dbapi: pypostgresql
|
||||
:connectstring: postgresql+pypostgresql://user:password@host:port/dbname[?key=value&key=value...]
|
||||
:url: http://python.projects.pgfoundry.org/
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, PGExecutionContext
|
||||
from sqlalchemy import processors
|
||||
from ... import util
|
||||
from ... import types as sqltypes
|
||||
from .base import PGDialect, PGExecutionContext
|
||||
from ... import processors
|
||||
|
||||
|
||||
class PGNumeric(sqltypes.Numeric):
|
||||
def bind_processor(self, dialect):
|
||||
@@ -28,9 +29,11 @@ class PGNumeric(sqltypes.Numeric):
|
||||
else:
|
||||
return processors.to_float
|
||||
|
||||
|
||||
class PGExecutionContext_pypostgresql(PGExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class PGDialect_pypostgresql(PGDialect):
|
||||
driver = 'pypostgresql'
|
||||
|
||||
@@ -48,8 +51,10 @@ class PGDialect_pypostgresql(PGDialect):
|
||||
colspecs = util.update_copy(
|
||||
PGDialect.colspecs,
|
||||
{
|
||||
sqltypes.Numeric : PGNumeric,
|
||||
sqltypes.Float: sqltypes.Float, # prevents PGNumeric from being used
|
||||
sqltypes.Numeric: PGNumeric,
|
||||
|
||||
# prevents PGNumeric from being used
|
||||
sqltypes.Float: sqltypes.Float,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# Copyright (C) 2013-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .base import ischema_names
|
||||
from ... import types as sqltypes
|
||||
|
||||
__all__ = ('INT4RANGE', 'INT8RANGE', 'NUMRANGE')
|
||||
|
||||
class RangeOperators(object):
|
||||
"""
|
||||
This mixin provides functionality for the Range Operators
|
||||
listed in Table 9-44 of the `postgres documentation`__ for Range
|
||||
Functions and Operators. It is used by all the range types
|
||||
provided in the ``postgres`` dialect and can likely be used for
|
||||
any range types you create yourself.
|
||||
|
||||
__ http://www.postgresql.org/docs/devel/static/functions-range.html
|
||||
|
||||
No extra support is provided for the Range Functions listed in
|
||||
Table 9-45 of the postgres documentation. For these, the normal
|
||||
:func:`~sqlalchemy.sql.expression.func` object should be used.
|
||||
|
||||
.. versionadded:: 0.8.2 Support for Postgresql RANGE operations.
|
||||
|
||||
"""
|
||||
|
||||
class comparator_factory(sqltypes.Concatenable.Comparator):
|
||||
"""Define comparison operations for range types."""
|
||||
|
||||
def __ne__(self, other):
|
||||
"Boolean expression. Returns true if two ranges are not equal"
|
||||
return self.expr.op('<>')(other)
|
||||
|
||||
def contains(self, other, **kw):
|
||||
"""Boolean expression. Returns true if the right hand operand,
|
||||
which can be an element or a range, is contained within the
|
||||
column.
|
||||
"""
|
||||
return self.expr.op('@>')(other)
|
||||
|
||||
def contained_by(self, other):
|
||||
"""Boolean expression. Returns true if the column is contained
|
||||
within the right hand operand.
|
||||
"""
|
||||
return self.expr.op('<@')(other)
|
||||
|
||||
def overlaps(self, other):
|
||||
"""Boolean expression. Returns true if the column overlaps
|
||||
(has points in common with) the right hand operand.
|
||||
"""
|
||||
return self.expr.op('&&')(other)
|
||||
|
||||
def strictly_left_of(self, other):
|
||||
"""Boolean expression. Returns true if the column is strictly
|
||||
left of the right hand operand.
|
||||
"""
|
||||
return self.expr.op('<<')(other)
|
||||
|
||||
__lshift__ = strictly_left_of
|
||||
|
||||
def strictly_right_of(self, other):
|
||||
"""Boolean expression. Returns true if the column is strictly
|
||||
right of the right hand operand.
|
||||
"""
|
||||
return self.expr.op('>>')(other)
|
||||
|
||||
__rshift__ = strictly_right_of
|
||||
|
||||
def not_extend_right_of(self, other):
|
||||
"""Boolean expression. Returns true if the range in the column
|
||||
does not extend right of the range in the operand.
|
||||
"""
|
||||
return self.expr.op('&<')(other)
|
||||
|
||||
def not_extend_left_of(self, other):
|
||||
"""Boolean expression. Returns true if the range in the column
|
||||
does not extend left of the range in the operand.
|
||||
"""
|
||||
return self.expr.op('&>')(other)
|
||||
|
||||
def adjacent_to(self, other):
|
||||
"""Boolean expression. Returns true if the range in the column
|
||||
is adjacent to the range in the operand.
|
||||
"""
|
||||
return self.expr.op('-|-')(other)
|
||||
|
||||
def __add__(self, other):
|
||||
"""Range expression. Returns the union of the two ranges.
|
||||
Will raise an exception if the resulting range is not
|
||||
contigous.
|
||||
"""
|
||||
return self.expr.op('+')(other)
|
||||
|
||||
class INT4RANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql INT4RANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'INT4RANGE'
|
||||
|
||||
ischema_names['int4range'] = INT4RANGE
|
||||
|
||||
class INT8RANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql INT8RANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'INT8RANGE'
|
||||
|
||||
ischema_names['int8range'] = INT8RANGE
|
||||
|
||||
class NUMRANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql NUMRANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'NUMRANGE'
|
||||
|
||||
ischema_names['numrange'] = NUMRANGE
|
||||
|
||||
class DATERANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql DATERANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'DATERANGE'
|
||||
|
||||
ischema_names['daterange'] = DATERANGE
|
||||
|
||||
class TSRANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql TSRANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'TSRANGE'
|
||||
|
||||
ischema_names['tsrange'] = TSRANGE
|
||||
|
||||
class TSTZRANGE(RangeOperators, sqltypes.TypeEngine):
|
||||
"""Represent the Postgresql TSTZRANGE type.
|
||||
|
||||
.. versionadded:: 0.8.2
|
||||
|
||||
"""
|
||||
|
||||
__visit_name__ = 'TSTZRANGE'
|
||||
|
||||
ischema_names['tstzrange'] = TSTZRANGE
|
||||
@@ -1,19 +1,21 @@
|
||||
# postgresql/zxjdbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the PostgreSQL database via the zxjdbc JDBC connector.
|
||||
"""
|
||||
.. dialect:: postgresql+zxjdbc
|
||||
:name: zxJDBC for Jython
|
||||
:dbapi: zxjdbc
|
||||
:connectstring: postgresql+zxjdbc://scott:tiger@localhost/db
|
||||
:driverurl: http://jdbc.postgresql.org/
|
||||
|
||||
JDBC Driver
|
||||
-----------
|
||||
|
||||
The official Postgresql JDBC driver is at http://jdbc.postgresql.org/.
|
||||
|
||||
"""
|
||||
from sqlalchemy.connectors.zxJDBC import ZxJDBCConnector
|
||||
from sqlalchemy.dialects.postgresql.base import PGDialect, PGExecutionContext
|
||||
from ...connectors.zxJDBC import ZxJDBCConnector
|
||||
from .base import PGDialect, PGExecutionContext
|
||||
|
||||
|
||||
class PGExecutionContext_zxjdbc(PGExecutionContext):
|
||||
|
||||
@@ -37,6 +39,7 @@ class PGDialect_zxjdbc(ZxJDBCConnector, PGDialect):
|
||||
self.DataHandler = PostgresqlDataHandler
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
return tuple(int(x) for x in connection.connection.dbversion.split('.'))
|
||||
parts = connection.connection.dbversion.split('.')
|
||||
return tuple(int(x) for x in parts)
|
||||
|
||||
dialect = PGDialect_zxjdbc
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sqlite/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -15,6 +15,7 @@ from sqlalchemy.dialects.sqlite.base import \
|
||||
NUMERIC, SMALLINT, TEXT, TIME, TIMESTAMP, VARCHAR, dialect
|
||||
|
||||
__all__ = (
|
||||
'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'FLOAT', 'INTEGER',
|
||||
'NUMERIC', 'SMALLINT', 'TEXT', 'TIME', 'TIMESTAMP', 'VARCHAR', 'dialect', 'REAL'
|
||||
)
|
||||
'BLOB', 'BOOLEAN', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'FLOAT',
|
||||
'INTEGER', 'NUMERIC', 'SMALLINT', 'TEXT', 'TIME', 'TIMESTAMP', 'VARCHAR',
|
||||
'REAL', 'dialect'
|
||||
)
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
# sqlite/base.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the SQLite database.
|
||||
"""
|
||||
.. dialect:: sqlite
|
||||
:name: SQLite
|
||||
|
||||
For information on connecting using a specific driver, see the documentation
|
||||
section regarding that driver.
|
||||
|
||||
Date and Time Types
|
||||
-------------------
|
||||
|
||||
SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite does not provide
|
||||
out of the box functionality for translating values between Python `datetime` objects
|
||||
and a SQLite-supported format. SQLAlchemy's own :class:`~sqlalchemy.types.DateTime`
|
||||
and related types provide date formatting and parsing functionality when SQlite is used.
|
||||
The implementation classes are :class:`~.sqlite.DATETIME`, :class:`~.sqlite.DATE` and :class:`~.sqlite.TIME`.
|
||||
These types represent dates and times as ISO formatted strings, which also nicely
|
||||
support ordering. There's no reliance on typical "libc" internals for these functions
|
||||
so historical dates are fully supported.
|
||||
SQLite does not have built-in DATE, TIME, or DATETIME types, and pysqlite
|
||||
does not provide out of the box functionality for translating values between
|
||||
Python `datetime` objects and a SQLite-supported format. SQLAlchemy's own
|
||||
:class:`~sqlalchemy.types.DateTime` and related types provide date formatting
|
||||
and parsing functionality when SQlite is used. The implementation classes are
|
||||
:class:`~.sqlite.DATETIME`, :class:`~.sqlite.DATE` and :class:`~.sqlite.TIME`.
|
||||
These types represent dates and times as ISO formatted strings, which also
|
||||
nicely support ordering. There's no reliance on typical "libc" internals
|
||||
for these functions so historical dates are fully supported.
|
||||
|
||||
Auto Incrementing Behavior
|
||||
--------------------------
|
||||
@@ -46,44 +47,47 @@ to the Table construct::
|
||||
Transaction Isolation Level
|
||||
---------------------------
|
||||
|
||||
:func:`.create_engine` accepts an ``isolation_level`` parameter which results in
|
||||
the command ``PRAGMA read_uncommitted <level>`` being invoked for every new
|
||||
connection. Valid values for this parameter are ``SERIALIZABLE`` and
|
||||
``READ UNCOMMITTED`` corresponding to a value of 0 and 1, respectively.
|
||||
:func:`.create_engine` accepts an ``isolation_level`` parameter which
|
||||
results in the command ``PRAGMA read_uncommitted <level>`` being invoked for
|
||||
every new connection. Valid values for this parameter are ``SERIALIZABLE``
|
||||
and ``READ UNCOMMITTED`` corresponding to a value of 0 and 1, respectively.
|
||||
See the section :ref:`pysqlite_serializable` for an important workaround
|
||||
when using serializable isolation with Pysqlite.
|
||||
|
||||
Database Locking Behavior / Concurrency
|
||||
---------------------------------------
|
||||
|
||||
Note that SQLite is not designed for a high level of concurrency. The database
|
||||
itself, being a file, is locked completely during write operations and within
|
||||
transactions, meaning exactly one connection has exclusive access to the database
|
||||
during this period - all other connections will be blocked during this time.
|
||||
Note that SQLite is not designed for a high level of concurrency. The
|
||||
database itself, being a file, is locked completely during write operations
|
||||
and within transactions, meaning exactly one connection has exclusive access
|
||||
to the database during this period - all other connections will be blocked
|
||||
during this time.
|
||||
|
||||
The Python DBAPI specification also calls for a connection model that is always
|
||||
in a transaction; there is no BEGIN method, only commit and rollback. This implies
|
||||
that a SQLite DBAPI driver would technically allow only serialized access to a
|
||||
particular database file at all times. The pysqlite driver attempts to ameliorate this by
|
||||
deferring the actual BEGIN statement until the first DML (INSERT, UPDATE, or
|
||||
DELETE) is received within a transaction. While this breaks serializable isolation,
|
||||
it at least delays the exclusive locking inherent in SQLite's design.
|
||||
in a transaction; there is no BEGIN method, only commit and rollback. This
|
||||
implies that a SQLite DBAPI driver would technically allow only serialized
|
||||
access to a particular database file at all times. The pysqlite driver
|
||||
attempts to ameliorate this by deferring the actual BEGIN statement until
|
||||
the first DML (INSERT, UPDATE, or DELETE) is received within a
|
||||
transaction. While this breaks serializable isolation, it at least delays
|
||||
the exclusive locking inherent in SQLite's design.
|
||||
|
||||
SQLAlchemy's default mode of usage with the ORM is known
|
||||
as "autocommit=False", which means the moment the :class:`.Session` begins to be
|
||||
used, a transaction is begun. As the :class:`.Session` is used, the autoflush
|
||||
feature, also on by default, will flush out pending changes to the database
|
||||
before each query. The effect of this is that a :class:`.Session` used in its
|
||||
default mode will often emit DML early on, long before the transaction is actually
|
||||
committed. This again will have the effect of serializing access to the SQLite
|
||||
database. If highly concurrent reads are desired against the SQLite database,
|
||||
it is advised that the autoflush feature be disabled, and potentially even
|
||||
that autocommit be re-enabled, which has the effect of each SQL statement and
|
||||
flush committing changes immediately.
|
||||
as "autocommit=False", which means the moment the :class:`.Session` begins to
|
||||
be used, a transaction is begun. As the :class:`.Session` is used, the
|
||||
autoflush feature, also on by default, will flush out pending changes to the
|
||||
database before each query. The effect of this is that a :class:`.Session`
|
||||
used in its default mode will often emit DML early on, long before the
|
||||
transaction is actually committed. This again will have the effect of
|
||||
serializing access to the SQLite database. If highly concurrent reads are
|
||||
desired against the SQLite database, it is advised that the autoflush feature
|
||||
be disabled, and potentially even that autocommit be re-enabled, which has
|
||||
the effect of each SQL statement and flush committing changes immediately.
|
||||
|
||||
For more information on SQLite's lack of concurrency by design, please
|
||||
see `Situations Where Another RDBMS May Work Better - High Concurrency <http://www.sqlite.org/whentouse.html>`_
|
||||
near the bottom of the page.
|
||||
see `Situations Where Another RDBMS May Work Better - High
|
||||
Concurrency <http://www.sqlite.org/whentouse.html>`_ near the bottom of
|
||||
the page.
|
||||
|
||||
.. _sqlite_foreign_keys:
|
||||
|
||||
@@ -123,7 +127,8 @@ for new connections through the usage of events::
|
||||
|
||||
"""
|
||||
|
||||
import datetime, re
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from sqlalchemy import sql, exc
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
@@ -132,8 +137,10 @@ from sqlalchemy import util
|
||||
from sqlalchemy.sql import compiler
|
||||
from sqlalchemy import processors
|
||||
|
||||
from sqlalchemy.types import BLOB, BOOLEAN, CHAR, DATE, DATETIME, DECIMAL,\
|
||||
FLOAT, REAL, INTEGER, NUMERIC, SMALLINT, TEXT, TIME, TIMESTAMP, VARCHAR
|
||||
from sqlalchemy.types import BIGINT, BLOB, BOOLEAN, CHAR,\
|
||||
DECIMAL, FLOAT, REAL, INTEGER, NUMERIC, SMALLINT, TEXT,\
|
||||
TIMESTAMP, VARCHAR
|
||||
|
||||
|
||||
class _DateTimeMixin(object):
|
||||
_reg = None
|
||||
@@ -146,15 +153,26 @@ class _DateTimeMixin(object):
|
||||
if storage_format is not None:
|
||||
self._storage_format = storage_format
|
||||
|
||||
def adapt(self, cls, **kw):
|
||||
if self._storage_format:
|
||||
kw["storage_format"] = self._storage_format
|
||||
if self._reg:
|
||||
kw["regexp"] = self._reg
|
||||
return util.constructor_copy(self, cls, **kw)
|
||||
|
||||
def literal_processor(self, dialect):
|
||||
bp = self.bind_processor(dialect)
|
||||
def process(value):
|
||||
return "'%s'" % bp(value)
|
||||
return process
|
||||
|
||||
|
||||
class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
"""Represent a Python datetime object in SQLite using a string.
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
"%04d-%02d-%02d %02d:%02d:%02d.%06d" % (value.year,
|
||||
value.month, value.day,
|
||||
value.hour, value.minute,
|
||||
value.second, value.microsecond)
|
||||
"%(year)04d-%(month)02d-%(day)02d %(hour)02d:%(min)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
e.g.::
|
||||
|
||||
@@ -167,37 +185,68 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
from sqlalchemy.dialects.sqlite import DATETIME
|
||||
|
||||
dt = DATETIME(
|
||||
storage_format="%04d/%02d/%02d %02d-%02d-%02d-%06d",
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)(?:-(\d+))?")
|
||||
)
|
||||
storage_format="%(year)04d/%(month)02d/%(day)02d %(hour)02d:%(min)02d:%(second)02d",
|
||||
regexp=r"(\d+)/(\d+)/(\d+) (\d+)-(\d+)-(\d+)"
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
tuple ``(value.year, value.month, value.day, value.hour,
|
||||
value.minute, value.second, value.microsecond)``, given a
|
||||
Python datetime.datetime() object.
|
||||
dict with keys year, month, day, hour, minute, second, and microsecond.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python datetime() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
incoming result rows. If the regexp contains named groups, the
|
||||
resulting match dict is applied to the Python datetime() constructor
|
||||
as keyword arguments. Otherwise, if positional groups are used, the
|
||||
the datetime() constructor is called with positional arguments via
|
||||
``*map(int, match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = "%04d-%02d-%02d %02d:%02d:%02d.%06d"
|
||||
_storage_format = (
|
||||
"%(year)04d-%(month)02d-%(day)02d "
|
||||
"%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
truncate_microseconds = kwargs.pop('truncate_microseconds', False)
|
||||
super(DATETIME, self).__init__(*args, **kwargs)
|
||||
if truncate_microseconds:
|
||||
assert 'storage_format' not in kwargs, "You can specify only "\
|
||||
"one of truncate_microseconds or storage_format."
|
||||
assert 'regexp' not in kwargs, "You can specify only one of "\
|
||||
"truncate_microseconds or regexp."
|
||||
self._storage_format = (
|
||||
"%(year)04d-%(month)02d-%(day)02d "
|
||||
"%(hour)02d:%(minute)02d:%(second)02d"
|
||||
)
|
||||
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
datetime_datetime = datetime.datetime
|
||||
datetime_date = datetime.date
|
||||
format = self._storage_format
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, datetime_datetime):
|
||||
return format % (value.year, value.month, value.day,
|
||||
value.hour, value.minute, value.second,
|
||||
value.microsecond)
|
||||
return format % {
|
||||
'year': value.year,
|
||||
'month': value.month,
|
||||
'day': value.day,
|
||||
'hour': value.hour,
|
||||
'minute': value.minute,
|
||||
'second': value.second,
|
||||
'microsecond': value.microsecond,
|
||||
}
|
||||
elif isinstance(value, datetime_date):
|
||||
return format % (value.year, value.month, value.day,
|
||||
0, 0, 0, 0)
|
||||
return format % {
|
||||
'year': value.year,
|
||||
'month': value.month,
|
||||
'day': value.day,
|
||||
'hour': 0,
|
||||
'minute': 0,
|
||||
'second': 0,
|
||||
'microsecond': 0,
|
||||
}
|
||||
else:
|
||||
raise TypeError("SQLite DateTime type only accepts Python "
|
||||
"datetime and date objects as input.")
|
||||
@@ -210,12 +259,13 @@ class DATETIME(_DateTimeMixin, sqltypes.DateTime):
|
||||
else:
|
||||
return processors.str_to_datetime
|
||||
|
||||
|
||||
class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
"""Represent a Python date object in SQLite using a string.
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
"%04d-%02d-%02d" % (value.year, value.month, value.day)
|
||||
"%(year)04d-%(month)02d-%(day)02d"
|
||||
|
||||
e.g.::
|
||||
|
||||
@@ -228,31 +278,36 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
from sqlalchemy.dialects.sqlite import DATE
|
||||
|
||||
d = DATE(
|
||||
storage_format="%02d/%02d/%02d",
|
||||
regexp=re.compile("(\d+)/(\d+)/(\d+)")
|
||||
storage_format="%(month)02d/%(day)02d/%(year)04d",
|
||||
regexp=re.compile("(?P<month>\d+)/(?P<day>\d+)/(?P<year>\d+)")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied to the
|
||||
tuple ``(value.year, value.month, value.day)``,
|
||||
given a Python datetime.date() object.
|
||||
dict with keys year, month, and day.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python date() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
incoming result rows. If the regexp contains named groups, the
|
||||
resulting match dict is applied to the Python date() constructor
|
||||
as keyword arguments. Otherwise, if positional groups are used, the
|
||||
the date() constructor is called with positional arguments via
|
||||
``*map(int, match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = "%04d-%02d-%02d"
|
||||
_storage_format = "%(year)04d-%(month)02d-%(day)02d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
datetime_date = datetime.date
|
||||
format = self._storage_format
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, datetime_date):
|
||||
return format % (value.year, value.month, value.day)
|
||||
return format % {
|
||||
'year': value.year,
|
||||
'month': value.month,
|
||||
'day': value.day,
|
||||
}
|
||||
else:
|
||||
raise TypeError("SQLite Date type only accepts Python "
|
||||
"date objects as input.")
|
||||
@@ -265,14 +320,13 @@ class DATE(_DateTimeMixin, sqltypes.Date):
|
||||
else:
|
||||
return processors.str_to_date
|
||||
|
||||
|
||||
class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
"""Represent a Python time object in SQLite using a string.
|
||||
|
||||
The default string storage format is::
|
||||
|
||||
"%02d:%02d:%02d.%06d" % (value.hour, value.minute,
|
||||
value.second,
|
||||
value.microsecond)
|
||||
"%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
e.g.::
|
||||
|
||||
@@ -285,32 +339,47 @@ class TIME(_DateTimeMixin, sqltypes.Time):
|
||||
from sqlalchemy.dialects.sqlite import TIME
|
||||
|
||||
t = TIME(
|
||||
storage_format="%02d-%02d-%02d-%06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
)
|
||||
storage_format="%(hour)02d-%(minute)02d-%(second)02d-%(microsecond)06d",
|
||||
regexp=re.compile("(\d+)-(\d+)-(\d+)-(?:-(\d+))?")
|
||||
)
|
||||
|
||||
:param storage_format: format string which will be applied
|
||||
to the tuple ``(value.hour, value.minute, value.second,
|
||||
value.microsecond)``, given a Python datetime.time() object.
|
||||
:param storage_format: format string which will be applied to the
|
||||
dict with keys hour, minute, second, and microsecond.
|
||||
|
||||
:param regexp: regular expression which will be applied to
|
||||
incoming result rows. The resulting match object is applied to
|
||||
the Python time() constructor via ``*map(int,
|
||||
match_obj.groups(0))``.
|
||||
|
||||
incoming result rows. If the regexp contains named groups, the
|
||||
resulting match dict is applied to the Python time() constructor
|
||||
as keyword arguments. Otherwise, if positional groups are used, the
|
||||
the time() constructor is called with positional arguments via
|
||||
``*map(int, match_obj.groups(0))``.
|
||||
"""
|
||||
|
||||
_storage_format = "%02d:%02d:%02d.%06d"
|
||||
_storage_format = "%(hour)02d:%(minute)02d:%(second)02d.%(microsecond)06d"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
truncate_microseconds = kwargs.pop('truncate_microseconds', False)
|
||||
super(TIME, self).__init__(*args, **kwargs)
|
||||
if truncate_microseconds:
|
||||
assert 'storage_format' not in kwargs, "You can specify only "\
|
||||
"one of truncate_microseconds or storage_format."
|
||||
assert 'regexp' not in kwargs, "You can specify only one of "\
|
||||
"truncate_microseconds or regexp."
|
||||
self._storage_format = "%(hour)02d:%(minute)02d:%(second)02d"
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
datetime_time = datetime.time
|
||||
format = self._storage_format
|
||||
|
||||
def process(value):
|
||||
if value is None:
|
||||
return None
|
||||
elif isinstance(value, datetime_time):
|
||||
return format % (value.hour, value.minute, value.second,
|
||||
value.microsecond)
|
||||
return format % {
|
||||
'hour': value.hour,
|
||||
'minute': value.minute,
|
||||
'second': value.second,
|
||||
'microsecond': value.microsecond,
|
||||
}
|
||||
else:
|
||||
raise TypeError("SQLite Time type only accepts Python "
|
||||
"time objects as input.")
|
||||
@@ -330,6 +399,7 @@ colspecs = {
|
||||
}
|
||||
|
||||
ischema_names = {
|
||||
'BIGINT': sqltypes.BIGINT,
|
||||
'BLOB': sqltypes.BLOB,
|
||||
'BOOL': sqltypes.BOOLEAN,
|
||||
'BOOLEAN': sqltypes.BOOLEAN,
|
||||
@@ -347,10 +417,11 @@ ischema_names = {
|
||||
'TIME': sqltypes.TIME,
|
||||
'TIMESTAMP': sqltypes.TIMESTAMP,
|
||||
'VARCHAR': sqltypes.VARCHAR,
|
||||
'NVARCHAR': sqltypes.NVARCHAR,
|
||||
'NCHAR': sqltypes.NCHAR,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class SQLiteCompiler(compiler.SQLCompiler):
|
||||
extract_map = util.update_copy(
|
||||
compiler.SQLCompiler.extract_map,
|
||||
@@ -391,7 +462,9 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
def visit_extract(self, extract, **kw):
|
||||
try:
|
||||
return "CAST(STRFTIME('%s', %s) AS INTEGER)" % (
|
||||
self.extract_map[extract.field], self.process(extract.expr, **kw))
|
||||
self.extract_map[extract.field],
|
||||
self.process(extract.expr, **kw)
|
||||
)
|
||||
except KeyError:
|
||||
raise exc.CompileError(
|
||||
"%s is not a valid extract argument." % extract.field)
|
||||
@@ -399,7 +472,7 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
def limit_clause(self, select):
|
||||
text = ""
|
||||
if select._limit is not None:
|
||||
text += "\n LIMIT " + self.process(sql.literal(select._limit))
|
||||
text += "\n LIMIT " + self.process(sql.literal(select._limit))
|
||||
if select._offset is not None:
|
||||
if select._limit is None:
|
||||
text += "\n LIMIT " + self.process(sql.literal(-1))
|
||||
@@ -416,7 +489,8 @@ class SQLiteCompiler(compiler.SQLCompiler):
|
||||
class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
def get_column_specification(self, column, **kwargs):
|
||||
colspec = self.preparer.format_column(column) + " " + self.dialect.type_compiler.process(column.type)
|
||||
coltype = self.dialect.type_compiler.process(column.type)
|
||||
colspec = self.preparer.format_column(column) + " " + coltype
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
@@ -424,12 +498,12 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
if not column.nullable:
|
||||
colspec += " NOT NULL"
|
||||
|
||||
if column.primary_key and \
|
||||
column.table.kwargs.get('sqlite_autoincrement', False) and \
|
||||
len(column.table.primary_key.columns) == 1 and \
|
||||
issubclass(column.type._type_affinity, sqltypes.Integer) and \
|
||||
not column.foreign_keys:
|
||||
colspec += " PRIMARY KEY AUTOINCREMENT"
|
||||
if (column.primary_key and
|
||||
column.table.kwargs.get('sqlite_autoincrement', False) and
|
||||
len(column.table.primary_key.columns) == 1 and
|
||||
issubclass(column.type._type_affinity, sqltypes.Integer) and
|
||||
not column.foreign_keys):
|
||||
colspec += " PRIMARY KEY AUTOINCREMENT"
|
||||
|
||||
return colspec
|
||||
|
||||
@@ -450,7 +524,7 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
|
||||
def visit_foreign_key_constraint(self, constraint):
|
||||
|
||||
local_table = constraint._elements.values()[0].parent.table
|
||||
local_table = list(constraint._elements.values())[0].parent.table
|
||||
remote_table = list(constraint._elements.values())[0].column.table
|
||||
|
||||
if local_table.schema != remote_table.schema:
|
||||
@@ -464,23 +538,15 @@ class SQLiteDDLCompiler(compiler.DDLCompiler):
|
||||
return preparer.format_table(table, use_schema=False)
|
||||
|
||||
def visit_create_index(self, create):
|
||||
index = create.element
|
||||
preparer = self.preparer
|
||||
text = "CREATE "
|
||||
if index.unique:
|
||||
text += "UNIQUE "
|
||||
text += "INDEX %s ON %s (%s)" \
|
||||
% (preparer.format_index(index,
|
||||
name=self._index_identifier(index.name)),
|
||||
preparer.format_table(index.table, use_schema=False),
|
||||
', '.join(preparer.quote(c.name, c.quote)
|
||||
for c in index.columns))
|
||||
return text
|
||||
return super(SQLiteDDLCompiler, self).\
|
||||
visit_create_index(create, include_table_schema=False)
|
||||
|
||||
|
||||
class SQLiteTypeCompiler(compiler.GenericTypeCompiler):
|
||||
def visit_large_binary(self, type_):
|
||||
return self.visit_BLOB(type_)
|
||||
|
||||
|
||||
class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
reserved_words = set([
|
||||
'add', 'after', 'all', 'alter', 'analyze', 'and', 'as', 'asc',
|
||||
@@ -492,14 +558,15 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
'drop', 'each', 'else', 'end', 'escape', 'except', 'exclusive',
|
||||
'explain', 'false', 'fail', 'for', 'foreign', 'from', 'full', 'glob',
|
||||
'group', 'having', 'if', 'ignore', 'immediate', 'in', 'index',
|
||||
'indexed', 'initially', 'inner', 'insert', 'instead', 'intersect', 'into', 'is',
|
||||
'isnull', 'join', 'key', 'left', 'like', 'limit', 'match', 'natural',
|
||||
'not', 'notnull', 'null', 'of', 'offset', 'on', 'or', 'order', 'outer',
|
||||
'plan', 'pragma', 'primary', 'query', 'raise', 'references',
|
||||
'reindex', 'rename', 'replace', 'restrict', 'right', 'rollback',
|
||||
'row', 'select', 'set', 'table', 'temp', 'temporary', 'then', 'to',
|
||||
'transaction', 'trigger', 'true', 'union', 'unique', 'update', 'using',
|
||||
'vacuum', 'values', 'view', 'virtual', 'when', 'where',
|
||||
'indexed', 'initially', 'inner', 'insert', 'instead', 'intersect',
|
||||
'into', 'is', 'isnull', 'join', 'key', 'left', 'like', 'limit',
|
||||
'match', 'natural', 'not', 'notnull', 'null', 'of', 'offset', 'on',
|
||||
'or', 'order', 'outer', 'plan', 'pragma', 'primary', 'query',
|
||||
'raise', 'references', 'reindex', 'rename', 'replace', 'restrict',
|
||||
'right', 'rollback', 'row', 'select', 'set', 'table', 'temp',
|
||||
'temporary', 'then', 'to', 'transaction', 'trigger', 'true', 'union',
|
||||
'unique', 'update', 'using', 'vacuum', 'values', 'view', 'virtual',
|
||||
'when', 'where',
|
||||
])
|
||||
|
||||
def format_index(self, index, use_schema=True, name=None):
|
||||
@@ -508,10 +575,14 @@ class SQLiteIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
if name is None:
|
||||
name = index.name
|
||||
result = self.quote(name, index.quote)
|
||||
if not self.omit_schema and use_schema and getattr(index.table, "schema", None):
|
||||
result = self.quote_schema(index.table.schema, index.table.quote_schema) + "." + result
|
||||
if (not self.omit_schema and
|
||||
use_schema and
|
||||
getattr(index.table, "schema", None)):
|
||||
result = self.quote_schema(
|
||||
index.table.schema, index.table.quote_schema) + "." + result
|
||||
return result
|
||||
|
||||
|
||||
class SQLiteExecutionContext(default.DefaultExecutionContext):
|
||||
@util.memoized_property
|
||||
def _preserve_raw_colnames(self):
|
||||
@@ -536,6 +607,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
supports_default_values = True
|
||||
supports_empty_insert = False
|
||||
supports_cast = True
|
||||
supports_multivalues_insert = True
|
||||
supports_right_nested_joins = False
|
||||
|
||||
default_paramstyle = 'qmark'
|
||||
execution_ctx_cls = SQLiteExecutionContext
|
||||
@@ -567,6 +640,9 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
self.dbapi.sqlite_version_info >= (3, 3, 8)
|
||||
self.supports_cast = \
|
||||
self.dbapi.sqlite_version_info >= (3, 2, 3)
|
||||
self.supports_multivalues_insert = \
|
||||
self.dbapi.sqlite_version_info >= (3, 7, 11)
|
||||
# http://www.sqlite.org/releaselog/3_7_11.html
|
||||
|
||||
# see http://www.sqlalchemy.org/trac/ticket/2568
|
||||
# as well as http://www.sqlite.org/src/info/600482d161
|
||||
@@ -575,9 +651,10 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
|
||||
|
||||
_isolation_lookup = {
|
||||
'READ UNCOMMITTED':1,
|
||||
'SERIALIZABLE':0
|
||||
'READ UNCOMMITTED': 1,
|
||||
'SERIALIZABLE': 0
|
||||
}
|
||||
|
||||
def set_isolation_level(self, connection, level):
|
||||
try:
|
||||
isolation_level = self._isolation_lookup[level.replace('_', ' ')]
|
||||
@@ -650,7 +727,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
cursor = _pragma_cursor(connection.execute("%stable_info(%s)" % (pragma, qtable)))
|
||||
statement = "%stable_info(%s)" % (pragma, qtable)
|
||||
cursor = _pragma_cursor(connection.execute(statement))
|
||||
row = cursor.fetchone()
|
||||
|
||||
# consume remaining rows, to work around
|
||||
@@ -716,9 +794,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
else:
|
||||
pragma = "PRAGMA "
|
||||
qtable = quote(table_name)
|
||||
c = _pragma_cursor(
|
||||
connection.execute("%stable_info(%s)" %
|
||||
(pragma, qtable)))
|
||||
statement = "%stable_info(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
|
||||
rows = c.fetchall()
|
||||
columns = []
|
||||
@@ -752,7 +829,7 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
coltype = sqltypes.NullType()
|
||||
|
||||
if default is not None:
|
||||
default = unicode(default)
|
||||
default = util.text_type(default)
|
||||
|
||||
return {
|
||||
'name': name,
|
||||
@@ -764,13 +841,13 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
}
|
||||
|
||||
@reflection.cache
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
cols = self.get_columns(connection, table_name, schema, **kw)
|
||||
pkeys = []
|
||||
for col in cols:
|
||||
if col['primary_key']:
|
||||
pkeys.append(col['name'])
|
||||
return pkeys
|
||||
return {'constrained_columns': pkeys, 'name': None}
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
@@ -830,7 +907,8 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
pragma = "PRAGMA "
|
||||
include_auto_indexes = kw.pop('include_auto_indexes', False)
|
||||
qtable = quote(table_name)
|
||||
c = _pragma_cursor(connection.execute("%sindex_list(%s)" % (pragma, qtable)))
|
||||
statement = "%sindex_list(%s)" % (pragma, qtable)
|
||||
c = _pragma_cursor(connection.execute(statement))
|
||||
indexes = []
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
@@ -838,13 +916,15 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
break
|
||||
# ignore implicit primary key index.
|
||||
# http://www.mail-archive.com/sqlite-users@sqlite.org/msg30517.html
|
||||
elif not include_auto_indexes and row[1].startswith('sqlite_autoindex'):
|
||||
elif (not include_auto_indexes and
|
||||
row[1].startswith('sqlite_autoindex')):
|
||||
continue
|
||||
|
||||
indexes.append(dict(name=row[1], column_names=[], unique=row[2]))
|
||||
# loop thru unique indexes to get the column names.
|
||||
for idx in indexes:
|
||||
c = connection.execute("%sindex_info(%s)" % (pragma, quote(idx['name'])))
|
||||
statement = "%sindex_info(%s)" % (pragma, quote(idx['name']))
|
||||
c = connection.execute(statement)
|
||||
cols = idx['column_names']
|
||||
while True:
|
||||
row = c.fetchone()
|
||||
@@ -853,6 +933,27 @@ class SQLiteDialect(default.DefaultDialect):
|
||||
cols.append(row[2])
|
||||
return indexes
|
||||
|
||||
@reflection.cache
|
||||
def get_unique_constraints(self, connection, table_name,
|
||||
schema=None, **kw):
|
||||
UNIQUE_SQL = """
|
||||
SELECT sql
|
||||
FROM
|
||||
sqlite_master
|
||||
WHERE
|
||||
type='table' AND
|
||||
name=:table_name
|
||||
"""
|
||||
c = connection.execute(UNIQUE_SQL, table_name=table_name)
|
||||
table_data = c.fetchone()[0]
|
||||
|
||||
UNIQUE_PATTERN = 'CONSTRAINT (\w+) UNIQUE \(([^\)]+)\)'
|
||||
return [
|
||||
{'name': name,
|
||||
'column_names': [col.strip(' "') for col in cols.split(',')]}
|
||||
for name, cols in re.findall(UNIQUE_PATTERN, table_data)
|
||||
]
|
||||
|
||||
|
||||
def _pragma_cursor(cursor):
|
||||
"""work around SQLite issue whereby cursor.description
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
# sqlite/pysqlite.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for the SQLite database via pysqlite.
|
||||
"""
|
||||
.. dialect:: sqlite+pysqlite
|
||||
:name: pysqlite
|
||||
:dbapi: sqlite3
|
||||
:connectstring: sqlite+pysqlite:///file_path
|
||||
:url: http://docs.python.org/library/sqlite3.html
|
||||
|
||||
Note that pysqlite is the same driver as the ``sqlite3``
|
||||
module included with the Python distribution.
|
||||
Note that ``pysqlite`` is the same driver as the ``sqlite3``
|
||||
module included with the Python distribution.
|
||||
|
||||
Driver
|
||||
------
|
||||
@@ -26,37 +31,36 @@ this explicitly::
|
||||
from sqlite3 import dbapi2 as sqlite
|
||||
e = create_engine('sqlite+pysqlite:///file.db', module=sqlite)
|
||||
|
||||
Full documentation on pysqlite is available at:
|
||||
`<http://www.initd.org/pub/software/pysqlite/doc/usage-guide.html>`_
|
||||
|
||||
Connect Strings
|
||||
---------------
|
||||
|
||||
The file specification for the SQLite database is taken as the "database" portion of
|
||||
the URL. Note that the format of a url is::
|
||||
The file specification for the SQLite database is taken as the "database"
|
||||
portion of the URL. Note that the format of a SQLAlchemy url is::
|
||||
|
||||
driver://user:pass@host/database
|
||||
|
||||
This means that the actual filename to be used starts with the characters to the
|
||||
**right** of the third slash. So connecting to a relative filepath looks like::
|
||||
This means that the actual filename to be used starts with the characters to
|
||||
the **right** of the third slash. So connecting to a relative filepath
|
||||
looks like::
|
||||
|
||||
# relative path
|
||||
e = create_engine('sqlite:///path/to/database.db')
|
||||
|
||||
An absolute path, which is denoted by starting with a slash, means you need **four**
|
||||
slashes::
|
||||
An absolute path, which is denoted by starting with a slash, means you
|
||||
need **four** slashes::
|
||||
|
||||
# absolute path
|
||||
e = create_engine('sqlite:////path/to/database.db')
|
||||
|
||||
To use a Windows path, regular drive specifications and backslashes can be used.
|
||||
Double backslashes are probably needed::
|
||||
To use a Windows path, regular drive specifications and backslashes can be
|
||||
used. Double backslashes are probably needed::
|
||||
|
||||
# absolute path on Windows
|
||||
e = create_engine('sqlite:///C:\\\\path\\\\to\\\\database.db')
|
||||
|
||||
The sqlite ``:memory:`` identifier is the default if no filepath is present. Specify
|
||||
``sqlite://`` and nothing else::
|
||||
The sqlite ``:memory:`` identifier is the default if no filepath is
|
||||
present. Specify ``sqlite://`` and nothing else::
|
||||
|
||||
# in-memory database
|
||||
e = create_engine('sqlite://')
|
||||
@@ -83,22 +87,24 @@ nor should be necessary, for use with SQLAlchemy, usage of PARSE_DECLTYPES
|
||||
can be forced if one configures "native_datetime=True" on create_engine()::
|
||||
|
||||
engine = create_engine('sqlite://',
|
||||
connect_args={'detect_types': sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES},
|
||||
native_datetime=True
|
||||
)
|
||||
connect_args={'detect_types': sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES},
|
||||
native_datetime=True
|
||||
)
|
||||
|
||||
With this flag enabled, the DATE and TIMESTAMP types (but note - not the DATETIME
|
||||
or TIME types...confused yet ?) will not perform any bind parameter or result
|
||||
processing. Execution of "func.current_date()" will return a string.
|
||||
With this flag enabled, the DATE and TIMESTAMP types (but note - not the
|
||||
DATETIME or TIME types...confused yet ?) will not perform any bind parameter
|
||||
or result processing. Execution of "func.current_date()" will return a string.
|
||||
"func.current_timestamp()" is registered as returning a DATETIME type in
|
||||
SQLAlchemy, so this function still receives SQLAlchemy-level result processing.
|
||||
|
||||
.. _pysqlite_threading_pooling:
|
||||
|
||||
Threading/Pooling Behavior
|
||||
---------------------------
|
||||
|
||||
Pysqlite's default behavior is to prohibit the usage of a single connection
|
||||
in more than one thread. This is originally intended to work with older versions
|
||||
of SQLite that did not support multithreaded operation under
|
||||
in more than one thread. This is originally intended to work with older
|
||||
versions of SQLite that did not support multithreaded operation under
|
||||
various circumstances. In particular, older SQLite versions
|
||||
did not allow a ``:memory:`` database to be used in multiple threads
|
||||
under any circumstances.
|
||||
@@ -114,17 +120,17 @@ thread-safety to make this usage worth it.
|
||||
|
||||
SQLAlchemy sets up pooling to work with Pysqlite's default behavior:
|
||||
|
||||
* When a ``:memory:`` SQLite database is specified, the dialect by default will use
|
||||
:class:`.SingletonThreadPool`. This pool maintains a single connection per
|
||||
thread, so that all access to the engine within the current thread use the
|
||||
same ``:memory:`` database - other threads would access a different
|
||||
``:memory:`` database.
|
||||
* When a file-based database is specified, the dialect will use :class:`.NullPool`
|
||||
as the source of connections. This pool closes and discards connections
|
||||
which are returned to the pool immediately. SQLite file-based connections
|
||||
have extremely low overhead, so pooling is not necessary. The scheme also
|
||||
prevents a connection from being used again in a different thread and works
|
||||
best with SQLite's coarse-grained file locking.
|
||||
* When a ``:memory:`` SQLite database is specified, the dialect by default
|
||||
will use :class:`.SingletonThreadPool`. This pool maintains a single
|
||||
connection per thread, so that all access to the engine within the current
|
||||
thread use the same ``:memory:`` database - other threads would access a
|
||||
different ``:memory:`` database.
|
||||
* When a file-based database is specified, the dialect will use
|
||||
:class:`.NullPool` as the source of connections. This pool closes and
|
||||
discards connections which are returned to the pool immediately. SQLite
|
||||
file-based connections have extremely low overhead, so pooling is not
|
||||
necessary. The scheme also prevents a connection from being used again in
|
||||
a different thread and works best with SQLite's coarse-grained file locking.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
Default selection of :class:`.NullPool` for SQLite file-based databases.
|
||||
@@ -137,9 +143,10 @@ Using a Memory Database in Multiple Threads
|
||||
|
||||
To use a ``:memory:`` database in a multithreaded scenario, the same connection
|
||||
object must be shared among threads, since the database exists
|
||||
only within the scope of that connection. The :class:`.StaticPool` implementation
|
||||
will maintain a single connection globally, and the ``check_same_thread`` flag
|
||||
can be passed to Pysqlite as ``False``::
|
||||
only within the scope of that connection. The
|
||||
:class:`.StaticPool` implementation will maintain a single connection
|
||||
globally, and the ``check_same_thread`` flag can be passed to Pysqlite
|
||||
as ``False``::
|
||||
|
||||
from sqlalchemy.pool import StaticPool
|
||||
engine = create_engine('sqlite://',
|
||||
@@ -152,13 +159,14 @@ version of SQLite.
|
||||
Using Temporary Tables with SQLite
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Due to the way SQLite deals with temporary tables, if you wish to use a temporary table
|
||||
in a file-based SQLite database across multiple checkouts from the connection pool, such
|
||||
as when using an ORM :class:`.Session` where the temporary table should continue to remain
|
||||
after :meth:`.commit` or :meth:`.rollback` is called,
|
||||
a pool which maintains a single connection must be used. Use :class:`.SingletonThreadPool`
|
||||
if the scope is only needed within the current thread, or :class:`.StaticPool` is scope is
|
||||
needed within multiple threads for this case::
|
||||
Due to the way SQLite deals with temporary tables, if you wish to use a
|
||||
temporary table in a file-based SQLite database across multiple checkouts
|
||||
from the connection pool, such as when using an ORM :class:`.Session` where
|
||||
the temporary table should continue to remain after :meth:`.Session.commit` or
|
||||
:meth:`.Session.rollback` is called, a pool which maintains a single connection must
|
||||
be used. Use :class:`.SingletonThreadPool` if the scope is only needed
|
||||
within the current thread, or :class:`.StaticPool` is scope is needed within
|
||||
multiple threads for this case::
|
||||
|
||||
# maintain the same connection per thread
|
||||
from sqlalchemy.pool import SingletonThreadPool
|
||||
@@ -171,17 +179,17 @@ needed within multiple threads for this case::
|
||||
engine = create_engine('sqlite:///mydb.db',
|
||||
poolclass=StaticPool)
|
||||
|
||||
Note that :class:`.SingletonThreadPool` should be configured for the number of threads
|
||||
that are to be used; beyond that number, connections will be closed out in a non deterministic
|
||||
way.
|
||||
Note that :class:`.SingletonThreadPool` should be configured for the number
|
||||
of threads that are to be used; beyond that number, connections will be
|
||||
closed out in a non deterministic way.
|
||||
|
||||
Unicode
|
||||
-------
|
||||
|
||||
The pysqlite driver only returns Python ``unicode`` objects in result sets, never
|
||||
plain strings, and accommodates ``unicode`` objects within bound parameter
|
||||
values in all cases. Regardless of the SQLAlchemy string type in use,
|
||||
string-based result values will by Python ``unicode`` in Python 2.
|
||||
The pysqlite driver only returns Python ``unicode`` objects in result sets,
|
||||
never plain strings, and accommodates ``unicode`` objects within bound
|
||||
parameter values in all cases. Regardless of the SQLAlchemy string type in
|
||||
use, string-based result values will by Python ``unicode`` in Python 2.
|
||||
The :class:`.Unicode` type should still be used to indicate those columns that
|
||||
require unicode, however, so that non-``unicode`` values passed inadvertently
|
||||
will emit a warning. Pysqlite will emit an error if a non-``unicode`` string
|
||||
@@ -221,6 +229,7 @@ from sqlalchemy import util
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class _SQLite_pysqliteTimeStamp(DATETIME):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect.native_datetime:
|
||||
@@ -234,6 +243,7 @@ class _SQLite_pysqliteTimeStamp(DATETIME):
|
||||
else:
|
||||
return DATETIME.result_processor(self, dialect, coltype)
|
||||
|
||||
|
||||
class _SQLite_pysqliteDate(DATE):
|
||||
def bind_processor(self, dialect):
|
||||
if dialect.native_datetime:
|
||||
@@ -247,19 +257,20 @@ class _SQLite_pysqliteDate(DATE):
|
||||
else:
|
||||
return DATE.result_processor(self, dialect, coltype)
|
||||
|
||||
|
||||
class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
default_paramstyle = 'qmark'
|
||||
|
||||
colspecs = util.update_copy(
|
||||
SQLiteDialect.colspecs,
|
||||
{
|
||||
sqltypes.Date:_SQLite_pysqliteDate,
|
||||
sqltypes.TIMESTAMP:_SQLite_pysqliteTimeStamp,
|
||||
sqltypes.Date: _SQLite_pysqliteDate,
|
||||
sqltypes.TIMESTAMP: _SQLite_pysqliteTimeStamp,
|
||||
}
|
||||
)
|
||||
|
||||
# Py3K
|
||||
#description_encoding = None
|
||||
if not util.py2k:
|
||||
description_encoding = None
|
||||
|
||||
driver = 'pysqlite'
|
||||
|
||||
@@ -279,9 +290,9 @@ class SQLiteDialect_pysqlite(SQLiteDialect):
|
||||
def dbapi(cls):
|
||||
try:
|
||||
from pysqlite2 import dbapi2 as sqlite
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
try:
|
||||
from sqlite3 import dbapi2 as sqlite #try the 2.5+ stdlib name.
|
||||
from sqlite3 import dbapi2 as sqlite # try 2.5+ stdlib name.
|
||||
except ImportError:
|
||||
raise e
|
||||
return sqlite
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
# sybase/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from sqlalchemy.dialects.sybase import base, pysybase, pyodbc
|
||||
|
||||
|
||||
from base import CHAR, VARCHAR, TIME, NCHAR, NVARCHAR,\
|
||||
TEXT,DATE,DATETIME, FLOAT, NUMERIC,\
|
||||
BIGINT,INT, INTEGER, SMALLINT, BINARY,\
|
||||
VARBINARY,UNITEXT,UNICHAR,UNIVARCHAR,\
|
||||
IMAGE,BIT,MONEY,SMALLMONEY,TINYINT
|
||||
|
||||
# default dialect
|
||||
base.dialect = pyodbc.dialect
|
||||
|
||||
from .base import CHAR, VARCHAR, TIME, NCHAR, NVARCHAR,\
|
||||
TEXT, DATE, DATETIME, FLOAT, NUMERIC,\
|
||||
BIGINT, INT, INTEGER, SMALLINT, BINARY,\
|
||||
VARBINARY, UNITEXT, UNICHAR, UNIVARCHAR,\
|
||||
IMAGE, BIT, MONEY, SMALLMONEY, TINYINT,\
|
||||
dialect
|
||||
|
||||
|
||||
__all__ = (
|
||||
'CHAR', 'VARCHAR', 'TIME', 'NCHAR', 'NVARCHAR',
|
||||
'TEXT','DATE','DATETIME', 'FLOAT', 'NUMERIC',
|
||||
'BIGINT','INT', 'INTEGER', 'SMALLINT', 'BINARY',
|
||||
'VARBINARY','UNITEXT','UNICHAR','UNIVARCHAR',
|
||||
'IMAGE','BIT','MONEY','SMALLMONEY','TINYINT',
|
||||
'dialect'
|
||||
'CHAR', 'VARCHAR', 'TIME', 'NCHAR', 'NVARCHAR',
|
||||
'TEXT', 'DATE', 'DATETIME', 'FLOAT', 'NUMERIC',
|
||||
'BIGINT', 'INT', 'INTEGER', 'SMALLINT', 'BINARY',
|
||||
'VARBINARY', 'UNITEXT', 'UNICHAR', 'UNIVARCHAR',
|
||||
'IMAGE', 'BIT', 'MONEY', 'SMALLMONEY', 'TINYINT',
|
||||
'dialect'
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# sybase/base.py
|
||||
# Copyright (C) 2010-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2010-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# get_select_precolumns(), limit_clause() implementation
|
||||
# copyright (C) 2007 Fisch Asset Management
|
||||
# AG http://www.fam.ch, with coding by Alexander Houben
|
||||
@@ -8,18 +8,21 @@
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Support for Sybase Adaptive Server Enterprise (ASE).
|
||||
"""
|
||||
|
||||
.. dialect:: sybase
|
||||
:name: Sybase
|
||||
|
||||
.. note::
|
||||
|
||||
The Sybase dialect functions on current SQLAlchemy versions
|
||||
but is not regularly tested, and may have many issues and
|
||||
caveats not currently handled. In particular, the table
|
||||
and database reflection features are not implemented.
|
||||
caveats not currently handled.
|
||||
|
||||
"""
|
||||
|
||||
import operator
|
||||
import re
|
||||
|
||||
from sqlalchemy.sql import compiler, expression, text, bindparam
|
||||
from sqlalchemy.engine import default, base, reflection
|
||||
from sqlalchemy import types as sqltypes
|
||||
@@ -28,10 +31,10 @@ from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import util, sql, exc
|
||||
|
||||
from sqlalchemy.types import CHAR, VARCHAR, TIME, NCHAR, NVARCHAR,\
|
||||
TEXT,DATE,DATETIME, FLOAT, NUMERIC,\
|
||||
BIGINT,INT, INTEGER, SMALLINT, BINARY,\
|
||||
TEXT, DATE, DATETIME, FLOAT, NUMERIC,\
|
||||
BIGINT, INT, INTEGER, SMALLINT, BINARY,\
|
||||
VARBINARY, DECIMAL, TIMESTAMP, Unicode,\
|
||||
UnicodeText
|
||||
UnicodeText, REAL
|
||||
|
||||
RESERVED_WORDS = set([
|
||||
"add", "all", "alter", "and",
|
||||
@@ -99,35 +102,44 @@ class _SybaseUnitypeMixin(object):
|
||||
def result_processor(self, dialect, coltype):
|
||||
def process(value):
|
||||
if value is not None:
|
||||
return str(value) #.decode("ucs-2")
|
||||
return str(value) # decode("ucs-2")
|
||||
else:
|
||||
return None
|
||||
return process
|
||||
|
||||
|
||||
class UNICHAR(_SybaseUnitypeMixin, sqltypes.Unicode):
|
||||
__visit_name__ = 'UNICHAR'
|
||||
|
||||
|
||||
class UNIVARCHAR(_SybaseUnitypeMixin, sqltypes.Unicode):
|
||||
__visit_name__ = 'UNIVARCHAR'
|
||||
|
||||
|
||||
class UNITEXT(_SybaseUnitypeMixin, sqltypes.UnicodeText):
|
||||
__visit_name__ = 'UNITEXT'
|
||||
|
||||
|
||||
class TINYINT(sqltypes.Integer):
|
||||
__visit_name__ = 'TINYINT'
|
||||
|
||||
|
||||
class BIT(sqltypes.TypeEngine):
|
||||
__visit_name__ = 'BIT'
|
||||
|
||||
|
||||
class MONEY(sqltypes.TypeEngine):
|
||||
__visit_name__ = "MONEY"
|
||||
|
||||
|
||||
class SMALLMONEY(sqltypes.TypeEngine):
|
||||
__visit_name__ = "SMALLMONEY"
|
||||
|
||||
|
||||
class UNIQUEIDENTIFIER(sqltypes.TypeEngine):
|
||||
__visit_name__ = "UNIQUEIDENTIFIER"
|
||||
|
||||
|
||||
class IMAGE(sqltypes.LargeBinary):
|
||||
__visit_name__ = 'IMAGE'
|
||||
|
||||
@@ -170,32 +182,68 @@ class SybaseTypeCompiler(compiler.GenericTypeCompiler):
|
||||
return "UNIQUEIDENTIFIER"
|
||||
|
||||
ischema_names = {
|
||||
'integer' : INTEGER,
|
||||
'unsigned int' : INTEGER, # TODO: unsigned flags
|
||||
'unsigned smallint' : SMALLINT, # TODO: unsigned flags
|
||||
'unsigned bigint' : BIGINT, # TODO: unsigned flags
|
||||
'bigint': BIGINT,
|
||||
'smallint' : SMALLINT,
|
||||
'tinyint' : TINYINT,
|
||||
'varchar' : VARCHAR,
|
||||
'long varchar' : TEXT, # TODO
|
||||
'char' : CHAR,
|
||||
'decimal' : DECIMAL,
|
||||
'numeric' : NUMERIC,
|
||||
'float' : FLOAT,
|
||||
'double' : NUMERIC, # TODO
|
||||
'binary' : BINARY,
|
||||
'varbinary' : VARBINARY,
|
||||
'bit': BIT,
|
||||
'image' : IMAGE,
|
||||
'timestamp': TIMESTAMP,
|
||||
'int': INTEGER,
|
||||
'integer': INTEGER,
|
||||
'smallint': SMALLINT,
|
||||
'tinyint': TINYINT,
|
||||
'unsigned bigint': BIGINT, # TODO: unsigned flags
|
||||
'unsigned int': INTEGER, # TODO: unsigned flags
|
||||
'unsigned smallint': SMALLINT, # TODO: unsigned flags
|
||||
'numeric': NUMERIC,
|
||||
'decimal': DECIMAL,
|
||||
'dec': DECIMAL,
|
||||
'float': FLOAT,
|
||||
'double': NUMERIC, # TODO
|
||||
'double precision': NUMERIC, # TODO
|
||||
'real': REAL,
|
||||
'smallmoney': SMALLMONEY,
|
||||
'money': MONEY,
|
||||
'smallmoney': MONEY,
|
||||
'smalldatetime': DATETIME,
|
||||
'datetime': DATETIME,
|
||||
'date': DATE,
|
||||
'time': TIME,
|
||||
'char': CHAR,
|
||||
'character': CHAR,
|
||||
'varchar': VARCHAR,
|
||||
'character varying': VARCHAR,
|
||||
'char varying': VARCHAR,
|
||||
'unichar': UNICHAR,
|
||||
'unicode character': UNIVARCHAR,
|
||||
'nchar': NCHAR,
|
||||
'national char': NCHAR,
|
||||
'national character': NCHAR,
|
||||
'nvarchar': NVARCHAR,
|
||||
'nchar varying': NVARCHAR,
|
||||
'national char varying': NVARCHAR,
|
||||
'national character varying': NVARCHAR,
|
||||
'text': TEXT,
|
||||
'unitext': UNITEXT,
|
||||
'binary': BINARY,
|
||||
'varbinary': VARBINARY,
|
||||
'image': IMAGE,
|
||||
'bit': BIT,
|
||||
|
||||
# not in documentation for ASE 15.7
|
||||
'long varchar': TEXT, # TODO
|
||||
'timestamp': TIMESTAMP,
|
||||
'uniqueidentifier': UNIQUEIDENTIFIER,
|
||||
|
||||
}
|
||||
|
||||
|
||||
class SybaseInspector(reflection.Inspector):
|
||||
|
||||
def __init__(self, conn):
|
||||
reflection.Inspector.__init__(self, conn)
|
||||
|
||||
def get_table_id(self, table_name, schema=None):
|
||||
"""Return the table id from `table_name` and `schema`."""
|
||||
|
||||
return self.dialect.get_table_id(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache)
|
||||
|
||||
|
||||
class SybaseExecutionContext(default.DefaultExecutionContext):
|
||||
_enable_identity_insert = False
|
||||
|
||||
@@ -243,12 +291,11 @@ class SybaseExecutionContext(default.DefaultExecutionContext):
|
||||
self.root_connection.connection.connection,
|
||||
True)
|
||||
|
||||
|
||||
def post_exec(self):
|
||||
if self.isddl:
|
||||
if self.isddl:
|
||||
self.set_ddl_autocommit(self.root_connection, False)
|
||||
|
||||
if self._enable_identity_insert:
|
||||
if self._enable_identity_insert:
|
||||
self.cursor.execute(
|
||||
"SET IDENTITY_INSERT %s OFF" %
|
||||
self.dialect.identifier_preparer.
|
||||
@@ -262,6 +309,7 @@ class SybaseExecutionContext(default.DefaultExecutionContext):
|
||||
cursor.close()
|
||||
return lastrowid
|
||||
|
||||
|
||||
class SybaseSQLCompiler(compiler.SQLCompiler):
|
||||
ansi_bind_rules = True
|
||||
|
||||
@@ -288,7 +336,7 @@ class SybaseSQLCompiler(compiler.SQLCompiler):
|
||||
# FIXME: sybase doesn't allow an offset without a limit
|
||||
# so use a huge value for TOP here
|
||||
s += "TOP 1000000 "
|
||||
s += "START AT %s " % (select._offset+1,)
|
||||
s += "START AT %s " % (select._offset + 1,)
|
||||
return s
|
||||
|
||||
def get_from_hint_text(self, table, text):
|
||||
@@ -303,6 +351,9 @@ class SybaseSQLCompiler(compiler.SQLCompiler):
|
||||
return 'DATEPART("%s", %s)' % (
|
||||
field, self.process(extract.expr, **kw))
|
||||
|
||||
def visit_now_func(self, fn, **kw):
|
||||
return "GETDATE()"
|
||||
|
||||
def for_update_clause(self, select):
|
||||
# "FOR UPDATE" is only allowed on "DECLARE CURSOR"
|
||||
# which SQLAlchemy doesn't use
|
||||
@@ -345,29 +396,31 @@ class SybaseDDLCompiler(compiler.DDLCompiler):
|
||||
# TODO: need correct syntax for this
|
||||
colspec += " IDENTITY(%s,%s)" % (start, increment)
|
||||
else:
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
if column.nullable is not None:
|
||||
if not column.nullable or column.primary_key:
|
||||
colspec += " NOT NULL"
|
||||
else:
|
||||
colspec += " NULL"
|
||||
|
||||
default = self.get_column_default_string(column)
|
||||
if default is not None:
|
||||
colspec += " DEFAULT " + default
|
||||
|
||||
return colspec
|
||||
|
||||
def visit_drop_index(self, drop):
|
||||
index = drop.element
|
||||
return "\nDROP INDEX %s.%s" % (
|
||||
self.preparer.quote_identifier(index.table.name),
|
||||
self.preparer.quote(
|
||||
self._index_identifier(index.name), index.quote)
|
||||
self._prepared_index_name(drop.element,
|
||||
include_schema=False)
|
||||
)
|
||||
|
||||
|
||||
class SybaseIdentifierPreparer(compiler.IdentifierPreparer):
|
||||
reserved_words = RESERVED_WORDS
|
||||
|
||||
|
||||
class SybaseDialect(default.DefaultDialect):
|
||||
name = 'sybase'
|
||||
supports_unicode_statements = False
|
||||
@@ -385,11 +438,12 @@ class SybaseDialect(default.DefaultDialect):
|
||||
statement_compiler = SybaseSQLCompiler
|
||||
ddl_compiler = SybaseDDLCompiler
|
||||
preparer = SybaseIdentifierPreparer
|
||||
inspector = SybaseInspector
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
return connection.scalar(
|
||||
text("SELECT user_name() as user_name",
|
||||
typemap={'user_name':Unicode})
|
||||
typemap={'user_name': Unicode})
|
||||
)
|
||||
|
||||
def initialize(self, connection):
|
||||
@@ -400,39 +454,361 @@ class SybaseDialect(default.DefaultDialect):
|
||||
else:
|
||||
self.max_identifier_length = 255
|
||||
|
||||
def get_table_id(self, connection, table_name, schema=None, **kw):
|
||||
"""Fetch the id for schema.table_name.
|
||||
|
||||
Several reflection methods require the table id. The idea for using
|
||||
this method is that it can be fetched one time and cached for
|
||||
subsequent calls.
|
||||
|
||||
"""
|
||||
|
||||
table_id = None
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
TABLEID_SQL = text("""
|
||||
SELECT o.id AS id
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid=u.uid
|
||||
WHERE u.name = :schema_name
|
||||
AND o.name = :table_name
|
||||
AND o.type in ('U', 'V')
|
||||
""")
|
||||
|
||||
if util.py2k:
|
||||
if isinstance(schema, unicode):
|
||||
schema = schema.encode("ascii")
|
||||
if isinstance(table_name, unicode):
|
||||
table_name = table_name.encode("ascii")
|
||||
result = connection.execute(TABLEID_SQL,
|
||||
schema_name=schema,
|
||||
table_name=table_name)
|
||||
table_id = result.scalar()
|
||||
if table_id is None:
|
||||
raise exc.NoSuchTableError(table_name)
|
||||
return table_id
|
||||
|
||||
@reflection.cache
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
COLUMN_SQL = text("""
|
||||
SELECT col.name AS name,
|
||||
t.name AS type,
|
||||
(col.status & 8) AS nullable,
|
||||
(col.status & 128) AS autoincrement,
|
||||
com.text AS 'default',
|
||||
col.prec AS precision,
|
||||
col.scale AS scale,
|
||||
col.length AS length
|
||||
FROM systypes t, syscolumns col LEFT OUTER JOIN syscomments com ON
|
||||
col.cdefault = com.id
|
||||
WHERE col.usertype = t.usertype
|
||||
AND col.id = :table_id
|
||||
ORDER BY col.colid
|
||||
""")
|
||||
|
||||
results = connection.execute(COLUMN_SQL, table_id=table_id)
|
||||
|
||||
columns = []
|
||||
for (name, type_, nullable, autoincrement, default, precision, scale,
|
||||
length) in results:
|
||||
col_info = self._get_column_info(name, type_, bool(nullable),
|
||||
bool(autoincrement), default, precision, scale,
|
||||
length)
|
||||
columns.append(col_info)
|
||||
|
||||
return columns
|
||||
|
||||
def _get_column_info(self, name, type_, nullable, autoincrement, default,
|
||||
precision, scale, length):
|
||||
|
||||
coltype = self.ischema_names.get(type_, None)
|
||||
|
||||
kwargs = {}
|
||||
|
||||
if coltype in (NUMERIC, DECIMAL):
|
||||
args = (precision, scale)
|
||||
elif coltype == FLOAT:
|
||||
args = (precision,)
|
||||
elif coltype in (CHAR, VARCHAR, UNICHAR, UNIVARCHAR, NCHAR, NVARCHAR):
|
||||
args = (length,)
|
||||
else:
|
||||
args = ()
|
||||
|
||||
if coltype:
|
||||
coltype = coltype(*args, **kwargs)
|
||||
#is this necessary
|
||||
#if is_array:
|
||||
# coltype = ARRAY(coltype)
|
||||
else:
|
||||
util.warn("Did not recognize type '%s' of column '%s'" %
|
||||
(type_, name))
|
||||
coltype = sqltypes.NULLTYPE
|
||||
|
||||
if default:
|
||||
default = re.sub("DEFAULT", "", default).strip()
|
||||
default = re.sub("^'(.*)'$", lambda m: m.group(1), default)
|
||||
else:
|
||||
default = None
|
||||
|
||||
column_info = dict(name=name, type=coltype, nullable=nullable,
|
||||
default=default, autoincrement=autoincrement)
|
||||
return column_info
|
||||
|
||||
@reflection.cache
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
table_cache = {}
|
||||
column_cache = {}
|
||||
foreign_keys = []
|
||||
|
||||
table_cache[table_id] = {"name": table_name, "schema": schema}
|
||||
|
||||
COLUMN_SQL = text("""
|
||||
SELECT c.colid AS id, c.name AS name
|
||||
FROM syscolumns c
|
||||
WHERE c.id = :table_id
|
||||
""")
|
||||
|
||||
results = connection.execute(COLUMN_SQL, table_id=table_id)
|
||||
columns = {}
|
||||
for col in results:
|
||||
columns[col["id"]] = col["name"]
|
||||
column_cache[table_id] = columns
|
||||
|
||||
REFCONSTRAINT_SQL = text("""
|
||||
SELECT o.name AS name, r.reftabid AS reftable_id,
|
||||
r.keycnt AS 'count',
|
||||
r.fokey1 AS fokey1, r.fokey2 AS fokey2, r.fokey3 AS fokey3,
|
||||
r.fokey4 AS fokey4, r.fokey5 AS fokey5, r.fokey6 AS fokey6,
|
||||
r.fokey7 AS fokey7, r.fokey1 AS fokey8, r.fokey9 AS fokey9,
|
||||
r.fokey10 AS fokey10, r.fokey11 AS fokey11, r.fokey12 AS fokey12,
|
||||
r.fokey13 AS fokey13, r.fokey14 AS fokey14, r.fokey15 AS fokey15,
|
||||
r.fokey16 AS fokey16,
|
||||
r.refkey1 AS refkey1, r.refkey2 AS refkey2, r.refkey3 AS refkey3,
|
||||
r.refkey4 AS refkey4, r.refkey5 AS refkey5, r.refkey6 AS refkey6,
|
||||
r.refkey7 AS refkey7, r.refkey1 AS refkey8, r.refkey9 AS refkey9,
|
||||
r.refkey10 AS refkey10, r.refkey11 AS refkey11,
|
||||
r.refkey12 AS refkey12, r.refkey13 AS refkey13,
|
||||
r.refkey14 AS refkey14, r.refkey15 AS refkey15,
|
||||
r.refkey16 AS refkey16
|
||||
FROM sysreferences r JOIN sysobjects o on r.tableid = o.id
|
||||
WHERE r.tableid = :table_id
|
||||
""")
|
||||
referential_constraints = connection.execute(REFCONSTRAINT_SQL,
|
||||
table_id=table_id)
|
||||
|
||||
REFTABLE_SQL = text("""
|
||||
SELECT o.name AS name, u.name AS 'schema'
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid = u.uid
|
||||
WHERE o.id = :table_id
|
||||
""")
|
||||
|
||||
for r in referential_constraints:
|
||||
reftable_id = r["reftable_id"]
|
||||
|
||||
if reftable_id not in table_cache:
|
||||
c = connection.execute(REFTABLE_SQL, table_id=reftable_id)
|
||||
reftable = c.fetchone()
|
||||
c.close()
|
||||
table_info = {"name": reftable["name"], "schema": None}
|
||||
if (schema is not None or
|
||||
reftable["schema"] != self.default_schema_name):
|
||||
table_info["schema"] = reftable["schema"]
|
||||
|
||||
table_cache[reftable_id] = table_info
|
||||
results = connection.execute(COLUMN_SQL, table_id=reftable_id)
|
||||
reftable_columns = {}
|
||||
for col in results:
|
||||
reftable_columns[col["id"]] = col["name"]
|
||||
column_cache[reftable_id] = reftable_columns
|
||||
|
||||
reftable = table_cache[reftable_id]
|
||||
reftable_columns = column_cache[reftable_id]
|
||||
|
||||
constrained_columns = []
|
||||
referred_columns = []
|
||||
for i in range(1, r["count"] + 1):
|
||||
constrained_columns.append(columns[r["fokey%i" % i]])
|
||||
referred_columns.append(reftable_columns[r["refkey%i" % i]])
|
||||
|
||||
fk_info = {
|
||||
"constrained_columns": constrained_columns,
|
||||
"referred_schema": reftable["schema"],
|
||||
"referred_table": reftable["name"],
|
||||
"referred_columns": referred_columns,
|
||||
"name": r["name"]
|
||||
}
|
||||
|
||||
foreign_keys.append(fk_info)
|
||||
|
||||
return foreign_keys
|
||||
|
||||
@reflection.cache
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
INDEX_SQL = text("""
|
||||
SELECT object_name(i.id) AS table_name,
|
||||
i.keycnt AS 'count',
|
||||
i.name AS name,
|
||||
(i.status & 0x2) AS 'unique',
|
||||
index_col(object_name(i.id), i.indid, 1) AS col_1,
|
||||
index_col(object_name(i.id), i.indid, 2) AS col_2,
|
||||
index_col(object_name(i.id), i.indid, 3) AS col_3,
|
||||
index_col(object_name(i.id), i.indid, 4) AS col_4,
|
||||
index_col(object_name(i.id), i.indid, 5) AS col_5,
|
||||
index_col(object_name(i.id), i.indid, 6) AS col_6,
|
||||
index_col(object_name(i.id), i.indid, 7) AS col_7,
|
||||
index_col(object_name(i.id), i.indid, 8) AS col_8,
|
||||
index_col(object_name(i.id), i.indid, 9) AS col_9,
|
||||
index_col(object_name(i.id), i.indid, 10) AS col_10,
|
||||
index_col(object_name(i.id), i.indid, 11) AS col_11,
|
||||
index_col(object_name(i.id), i.indid, 12) AS col_12,
|
||||
index_col(object_name(i.id), i.indid, 13) AS col_13,
|
||||
index_col(object_name(i.id), i.indid, 14) AS col_14,
|
||||
index_col(object_name(i.id), i.indid, 15) AS col_15,
|
||||
index_col(object_name(i.id), i.indid, 16) AS col_16
|
||||
FROM sysindexes i, sysobjects o
|
||||
WHERE o.id = i.id
|
||||
AND o.id = :table_id
|
||||
AND (i.status & 2048) = 0
|
||||
AND i.indid BETWEEN 1 AND 254
|
||||
""")
|
||||
|
||||
results = connection.execute(INDEX_SQL, table_id=table_id)
|
||||
indexes = []
|
||||
for r in results:
|
||||
column_names = []
|
||||
for i in range(1, r["count"]):
|
||||
column_names.append(r["col_%i" % (i,)])
|
||||
index_info = {"name": r["name"],
|
||||
"unique": bool(r["unique"]),
|
||||
"column_names": column_names}
|
||||
indexes.append(index_info)
|
||||
|
||||
return indexes
|
||||
|
||||
@reflection.cache
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
table_id = self.get_table_id(connection, table_name, schema,
|
||||
info_cache=kw.get("info_cache"))
|
||||
|
||||
PK_SQL = text("""
|
||||
SELECT object_name(i.id) AS table_name,
|
||||
i.keycnt AS 'count',
|
||||
i.name AS name,
|
||||
index_col(object_name(i.id), i.indid, 1) AS pk_1,
|
||||
index_col(object_name(i.id), i.indid, 2) AS pk_2,
|
||||
index_col(object_name(i.id), i.indid, 3) AS pk_3,
|
||||
index_col(object_name(i.id), i.indid, 4) AS pk_4,
|
||||
index_col(object_name(i.id), i.indid, 5) AS pk_5,
|
||||
index_col(object_name(i.id), i.indid, 6) AS pk_6,
|
||||
index_col(object_name(i.id), i.indid, 7) AS pk_7,
|
||||
index_col(object_name(i.id), i.indid, 8) AS pk_8,
|
||||
index_col(object_name(i.id), i.indid, 9) AS pk_9,
|
||||
index_col(object_name(i.id), i.indid, 10) AS pk_10,
|
||||
index_col(object_name(i.id), i.indid, 11) AS pk_11,
|
||||
index_col(object_name(i.id), i.indid, 12) AS pk_12,
|
||||
index_col(object_name(i.id), i.indid, 13) AS pk_13,
|
||||
index_col(object_name(i.id), i.indid, 14) AS pk_14,
|
||||
index_col(object_name(i.id), i.indid, 15) AS pk_15,
|
||||
index_col(object_name(i.id), i.indid, 16) AS pk_16
|
||||
FROM sysindexes i, sysobjects o
|
||||
WHERE o.id = i.id
|
||||
AND o.id = :table_id
|
||||
AND (i.status & 2048) = 2048
|
||||
AND i.indid BETWEEN 1 AND 254
|
||||
""")
|
||||
|
||||
results = connection.execute(PK_SQL, table_id=table_id)
|
||||
pks = results.fetchone()
|
||||
results.close()
|
||||
|
||||
constrained_columns = []
|
||||
for i in range(1, pks["count"] + 1):
|
||||
constrained_columns.append(pks["pk_%i" % (i,)])
|
||||
return {"constrained_columns": constrained_columns,
|
||||
"name": pks["name"]}
|
||||
|
||||
@reflection.cache
|
||||
def get_schema_names(self, connection, **kw):
|
||||
|
||||
SCHEMA_SQL = text("SELECT u.name AS name FROM sysusers u")
|
||||
|
||||
schemas = connection.execute(SCHEMA_SQL)
|
||||
|
||||
return [s["name"] for s in schemas]
|
||||
|
||||
@reflection.cache
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
result = connection.execute(
|
||||
text("select sysobjects.name from sysobjects, sysusers "
|
||||
"where sysobjects.uid=sysusers.uid and "
|
||||
"sysusers.name=:schemaname and "
|
||||
"sysobjects.type='U'",
|
||||
bindparams=[
|
||||
bindparam('schemaname', schema)
|
||||
])
|
||||
)
|
||||
return [r[0] for r in result]
|
||||
TABLE_SQL = text("""
|
||||
SELECT o.name AS name
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid = u.uid
|
||||
WHERE u.name = :schema_name
|
||||
AND o.type = 'U'
|
||||
""")
|
||||
|
||||
def has_table(self, connection, tablename, schema=None):
|
||||
if util.py2k:
|
||||
if isinstance(schema, unicode):
|
||||
schema = schema.encode("ascii")
|
||||
|
||||
tables = connection.execute(TABLE_SQL, schema_name=schema)
|
||||
|
||||
return [t["name"] for t in tables]
|
||||
|
||||
@reflection.cache
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
result = connection.execute(
|
||||
text("select sysobjects.name from sysobjects, sysusers "
|
||||
"where sysobjects.uid=sysusers.uid and "
|
||||
"sysobjects.name=:tablename and "
|
||||
"sysusers.name=:schemaname and "
|
||||
"sysobjects.type='U'",
|
||||
bindparams=[
|
||||
bindparam('tablename', tablename),
|
||||
bindparam('schemaname', schema)
|
||||
])
|
||||
)
|
||||
return result.scalar() is not None
|
||||
VIEW_DEF_SQL = text("""
|
||||
SELECT c.text
|
||||
FROM syscomments c JOIN sysobjects o ON c.id = o.id
|
||||
WHERE o.name = :view_name
|
||||
AND o.type = 'V'
|
||||
""")
|
||||
|
||||
def reflecttable(self, connection, table, include_columns):
|
||||
raise NotImplementedError()
|
||||
if util.py2k:
|
||||
if isinstance(view_name, unicode):
|
||||
view_name = view_name.encode("ascii")
|
||||
|
||||
view = connection.execute(VIEW_DEF_SQL, view_name=view_name)
|
||||
|
||||
return view.scalar()
|
||||
|
||||
@reflection.cache
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
if schema is None:
|
||||
schema = self.default_schema_name
|
||||
|
||||
VIEW_SQL = text("""
|
||||
SELECT o.name AS name
|
||||
FROM sysobjects o JOIN sysusers u ON o.uid = u.uid
|
||||
WHERE u.name = :schema_name
|
||||
AND o.type = 'V'
|
||||
""")
|
||||
|
||||
if util.py2k:
|
||||
if isinstance(schema, unicode):
|
||||
schema = schema.encode("ascii")
|
||||
views = connection.execute(VIEW_SQL, schema_name=schema)
|
||||
|
||||
return [v["name"] for v in views]
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
try:
|
||||
self.get_table_id(connection, table_name, schema)
|
||||
except exc.NoSuchTableError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
# sybase/mxodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for Sybase via mxodbc.
|
||||
|
||||
This dialect is a stub only and is likely non functional at this time.
|
||||
.. dialect:: sybase+mxodbc
|
||||
:name: mxODBC
|
||||
:dbapi: mxodbc
|
||||
:connectstring: sybase+mxodbc://<username>:<password>@<dsnname>
|
||||
:url: http://www.egenix.com/
|
||||
|
||||
.. note::
|
||||
|
||||
This dialect is a stub only and is likely non functional at this time.
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect, SybaseExecutionContext
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect
|
||||
from sqlalchemy.dialects.sybase.base import SybaseExecutionContext
|
||||
from sqlalchemy.connectors.mxodbc import MxODBCConnector
|
||||
|
||||
|
||||
class SybaseExecutionContext_mxodbc(SybaseExecutionContext):
|
||||
pass
|
||||
|
||||
|
||||
class SybaseDialect_mxodbc(MxODBCConnector, SybaseDialect):
|
||||
execution_ctx_cls = SybaseExecutionContext_mxodbc
|
||||
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
# sybase/pyodbc.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for Sybase via pyodbc.
|
||||
.. dialect:: sybase+pyodbc
|
||||
:name: PyODBC
|
||||
:dbapi: pyodbc
|
||||
:connectstring: sybase+pyodbc://<username>:<password>@<dsnname>[/<database>]
|
||||
:url: http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
http://pypi.python.org/pypi/pyodbc/
|
||||
|
||||
Connect strings are of the form::
|
||||
|
||||
sybase+pyodbc://<username>:<password>@<dsn>/
|
||||
sybase+pyodbc://<username>:<password>@<host>/<database>
|
||||
|
||||
Unicode Support
|
||||
---------------
|
||||
@@ -37,8 +35,9 @@ Currently *not* supported are::
|
||||
from sqlalchemy.dialects.sybase.base import SybaseDialect,\
|
||||
SybaseExecutionContext
|
||||
from sqlalchemy.connectors.pyodbc import PyODBCConnector
|
||||
from sqlalchemy import types as sqltypes, util, processors
|
||||
from sqlalchemy.util.compat import decimal
|
||||
from sqlalchemy import types as sqltypes, processors
|
||||
import decimal
|
||||
|
||||
|
||||
class _SybNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""Turns Decimals with adjusted() < -6 into floats.
|
||||
@@ -50,7 +49,7 @@ class _SybNumeric_pyodbc(sqltypes.Numeric):
|
||||
"""
|
||||
|
||||
def bind_processor(self, dialect):
|
||||
super_process = super(_SybNumeric_pyodbc,self).\
|
||||
super_process = super(_SybNumeric_pyodbc, self).\
|
||||
bind_processor(dialect)
|
||||
|
||||
def process(value):
|
||||
@@ -66,6 +65,7 @@ class _SybNumeric_pyodbc(sqltypes.Numeric):
|
||||
return value
|
||||
return process
|
||||
|
||||
|
||||
class SybaseExecutionContext_pyodbc(SybaseExecutionContext):
|
||||
def set_ddl_autocommit(self, connection, value):
|
||||
if value:
|
||||
@@ -73,11 +73,12 @@ class SybaseExecutionContext_pyodbc(SybaseExecutionContext):
|
||||
else:
|
||||
connection.autocommit = False
|
||||
|
||||
|
||||
class SybaseDialect_pyodbc(PyODBCConnector, SybaseDialect):
|
||||
execution_ctx_cls = SybaseExecutionContext_pyodbc
|
||||
|
||||
colspecs = {
|
||||
sqltypes.Numeric:_SybNumeric_pyodbc,
|
||||
sqltypes.Numeric: _SybNumeric_pyodbc,
|
||||
}
|
||||
|
||||
dialect = SybaseDialect_pyodbc
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
# sybase/pysybase.py
|
||||
# Copyright (C) 2010-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2010-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Support for Sybase via the python-sybase driver.
|
||||
|
||||
http://python-sybase.sourceforge.net/
|
||||
|
||||
Connect strings are of the form::
|
||||
|
||||
sybase+pysybase://<username>:<password>@<dsn>/[database name]
|
||||
.. dialect:: sybase+pysybase
|
||||
:name: Python-Sybase
|
||||
:dbapi: Sybase
|
||||
:connectstring: sybase+pysybase://<username>:<password>@<dsn>/[database name]
|
||||
:url: http://python-sybase.sourceforge.net/
|
||||
|
||||
Unicode Support
|
||||
---------------
|
||||
@@ -33,6 +31,7 @@ class _SybNumeric(sqltypes.Numeric):
|
||||
else:
|
||||
return sqltypes.Numeric.result_processor(self, dialect, type_)
|
||||
|
||||
|
||||
class SybaseExecutionContext_pysybase(SybaseExecutionContext):
|
||||
|
||||
def set_ddl_autocommit(self, dbapi_connection, value):
|
||||
@@ -55,14 +54,15 @@ class SybaseSQLCompiler_pysybase(SybaseSQLCompiler):
|
||||
def bindparam_string(self, name, **kw):
|
||||
return "@" + name
|
||||
|
||||
|
||||
class SybaseDialect_pysybase(SybaseDialect):
|
||||
driver = 'pysybase'
|
||||
execution_ctx_cls = SybaseExecutionContext_pysybase
|
||||
statement_compiler = SybaseSQLCompiler_pysybase
|
||||
|
||||
colspecs={
|
||||
sqltypes.Numeric:_SybNumeric,
|
||||
sqltypes.Float:sqltypes.Float
|
||||
colspecs = {
|
||||
sqltypes.Numeric: _SybNumeric,
|
||||
sqltypes.Float: sqltypes.Float
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/__init__.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -50,54 +50,47 @@ url.py
|
||||
within a URL.
|
||||
"""
|
||||
|
||||
# not sure what this was used for
|
||||
#import sqlalchemy.databases
|
||||
from .interfaces import (
|
||||
Connectable,
|
||||
Dialect,
|
||||
ExecutionContext,
|
||||
|
||||
from sqlalchemy.engine.base import (
|
||||
# backwards compat
|
||||
Compiled,
|
||||
TypeCompiler
|
||||
)
|
||||
|
||||
from .base import (
|
||||
Connection,
|
||||
Engine,
|
||||
NestedTransaction,
|
||||
RootTransaction,
|
||||
Transaction,
|
||||
TwoPhaseTransaction,
|
||||
)
|
||||
|
||||
from .result import (
|
||||
BufferedColumnResultProxy,
|
||||
BufferedColumnRow,
|
||||
BufferedRowResultProxy,
|
||||
Compiled,
|
||||
Connectable,
|
||||
Connection,
|
||||
Dialect,
|
||||
Engine,
|
||||
ExecutionContext,
|
||||
NestedTransaction,
|
||||
FullyBufferedResultProxy,
|
||||
ResultProxy,
|
||||
RootTransaction,
|
||||
RowProxy,
|
||||
Transaction,
|
||||
TwoPhaseTransaction,
|
||||
TypeCompiler
|
||||
)
|
||||
from sqlalchemy.engine import strategies
|
||||
from sqlalchemy import util
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BufferedColumnResultProxy',
|
||||
'BufferedColumnRow',
|
||||
'BufferedRowResultProxy',
|
||||
'Compiled',
|
||||
'Connectable',
|
||||
'Connection',
|
||||
'Dialect',
|
||||
'Engine',
|
||||
'ExecutionContext',
|
||||
'NestedTransaction',
|
||||
'ResultProxy',
|
||||
'RootTransaction',
|
||||
'RowProxy',
|
||||
'Transaction',
|
||||
'TwoPhaseTransaction',
|
||||
'TypeCompiler',
|
||||
'create_engine',
|
||||
'engine_from_config',
|
||||
)
|
||||
|
||||
from .util import (
|
||||
connection_memoize
|
||||
)
|
||||
|
||||
|
||||
from . import util, strategies
|
||||
|
||||
# backwards compat
|
||||
from ..sql import ddl
|
||||
|
||||
default_strategy = 'plain'
|
||||
|
||||
|
||||
def create_engine(*args, **kwargs):
|
||||
"""Create a new :class:`.Engine` instance.
|
||||
|
||||
@@ -117,11 +110,11 @@ def create_engine(*args, **kwargs):
|
||||
the URL can be an instance of :class:`~sqlalchemy.engine.url.URL`.
|
||||
|
||||
``**kwargs`` takes a wide variety of options which are routed
|
||||
towards their appropriate components. Arguments may be
|
||||
specific to the :class:`.Engine`, the underlying :class:`.Dialect`, as well as the
|
||||
:class:`.Pool`. Specific dialects also accept keyword arguments that
|
||||
are unique to that dialect. Here, we describe the parameters
|
||||
that are common to most :func:`.create_engine()` usage.
|
||||
towards their appropriate components. Arguments may be specific
|
||||
to the :class:`.Engine`, the underlying :class:`.Dialect`, as well as
|
||||
the :class:`.Pool`. Specific dialects also accept keyword
|
||||
arguments that are unique to that dialect. Here, we describe the
|
||||
parameters that are common to most :func:`.create_engine()` usage.
|
||||
|
||||
Once established, the newly resulting :class:`.Engine` will
|
||||
request a connection from the underlying :class:`.Pool` once
|
||||
@@ -133,15 +126,17 @@ def create_engine(*args, **kwargs):
|
||||
|
||||
See also:
|
||||
|
||||
:ref:`engines_toplevel`
|
||||
:doc:`/core/engines`
|
||||
|
||||
:ref:`connections_toplevel`
|
||||
|
||||
:param assert_unicode: Deprecated. This flag
|
||||
sets an engine-wide default value for
|
||||
the ``assert_unicode`` flag on the
|
||||
:class:`.String` type - see that
|
||||
type for further details.
|
||||
:param case_sensitive=True: if False, result column names
|
||||
will match in a case-insensitive fashion, that is,
|
||||
``row['SomeColumn']``.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
By default, result row names match case-sensitively.
|
||||
In version 0.7 and prior, all matches were case-insensitive.
|
||||
|
||||
:param connect_args: a dictionary of options which will be
|
||||
passed directly to the DBAPI's ``connect()`` method as
|
||||
@@ -231,7 +226,7 @@ def create_engine(*args, **kwargs):
|
||||
|
||||
:param execution_options: Dictionary execution options which will
|
||||
be applied to all connections. See
|
||||
:meth:`~sqlalchemy.engine.base.Connection.execution_options`
|
||||
:meth:`~sqlalchemy.engine.Connection.execution_options`
|
||||
|
||||
:param implicit_returning=True: When ``True``, a RETURNING-
|
||||
compatible construct, if available, will be used to
|
||||
@@ -262,13 +257,13 @@ def create_engine(*args, **kwargs):
|
||||
opened above and beyond the pool_size setting, which defaults
|
||||
to five. this is only used with :class:`~sqlalchemy.pool.QueuePool`.
|
||||
|
||||
:param module=None: reference to a Python module object (the module itself, not
|
||||
its string name). Specifies an alternate DBAPI module to be used
|
||||
by the engine's dialect. Each sub-dialect references a specific DBAPI which
|
||||
will be imported before first connect. This parameter causes the
|
||||
import to be bypassed, and the given module to be used instead.
|
||||
Can be used for testing of DBAPIs as well as to inject "mock"
|
||||
DBAPI implementations into the :class:`.Engine`.
|
||||
:param module=None: reference to a Python module object (the module
|
||||
itself, not its string name). Specifies an alternate DBAPI module to
|
||||
be used by the engine's dialect. Each sub-dialect references a
|
||||
specific DBAPI which will be imported before first connect. This
|
||||
parameter causes the import to be bypassed, and the given module to
|
||||
be used instead. Can be used for testing of DBAPIs as well as to
|
||||
inject "mock" DBAPI implementations into the :class:`.Engine`.
|
||||
|
||||
:param pool=None: an already-constructed instance of
|
||||
:class:`~sqlalchemy.pool.Pool`, such as a
|
||||
@@ -291,7 +286,8 @@ def create_engine(*args, **kwargs):
|
||||
id.
|
||||
|
||||
:param pool_size=5: the number of connections to keep open
|
||||
inside the connection pool. This used with :class:`~sqlalchemy.pool.QueuePool` as
|
||||
inside the connection pool. This used with
|
||||
:class:`~sqlalchemy.pool.QueuePool` as
|
||||
well as :class:`~sqlalchemy.pool.SingletonThreadPool`. With
|
||||
:class:`~sqlalchemy.pool.QueuePool`, a ``pool_size`` setting
|
||||
of 0 indicates no limit; to disable pooling, set ``poolclass`` to
|
||||
@@ -325,7 +321,8 @@ def create_engine(*args, **kwargs):
|
||||
:ref:`threadlocal_strategy`;
|
||||
* the ``mock`` strategy, which dispatches all statement
|
||||
execution to a function passed as the argument ``executor``.
|
||||
See `example in the FAQ <http://www.sqlalchemy.org/trac/wiki/FAQ#HowcanIgettheCREATETABLEDROPTABLEoutputasastring>`_.
|
||||
See `example in the FAQ
|
||||
<http://www.sqlalchemy.org/trac/wiki/FAQ#HowcanIgettheCREATETABLEDROPTABLEoutputasastring>`_.
|
||||
|
||||
:param executor=None: a function taking arguments
|
||||
``(sql, *multiparams, **params)``, to which the ``mock`` strategy will
|
||||
@@ -337,6 +334,7 @@ def create_engine(*args, **kwargs):
|
||||
strategy = strategies.strategies[strategy]
|
||||
return strategy.create(*args, **kwargs)
|
||||
|
||||
|
||||
def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
|
||||
"""Create a new Engine instance using a configuration dictionary.
|
||||
|
||||
@@ -350,27 +348,16 @@ def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
|
||||
arguments.
|
||||
"""
|
||||
|
||||
opts = _coerce_config(configuration, prefix)
|
||||
opts.update(kwargs)
|
||||
url = opts.pop('url')
|
||||
return create_engine(url, **opts)
|
||||
|
||||
def _coerce_config(configuration, prefix):
|
||||
"""Convert configuration values to expected types."""
|
||||
|
||||
options = dict((key[len(prefix):], configuration[key])
|
||||
for key in configuration
|
||||
if key.startswith(prefix))
|
||||
for option, type_ in (
|
||||
('convert_unicode', util.bool_or_str('force')),
|
||||
('pool_timeout', int),
|
||||
('echo', util.bool_or_str('debug')),
|
||||
('echo_pool', util.bool_or_str('debug')),
|
||||
('pool_recycle', int),
|
||||
('pool_size', int),
|
||||
('max_overflow', int),
|
||||
('pool_threadlocal', bool),
|
||||
('use_native_unicode', bool),
|
||||
):
|
||||
util.coerce_kw_type(options, option, type_)
|
||||
return options
|
||||
options['_coerce_config'] = True
|
||||
options.update(kwargs)
|
||||
url = options.pop('url')
|
||||
return create_engine(url, **options)
|
||||
|
||||
|
||||
__all__ = (
|
||||
'create_engine',
|
||||
'engine_from_config',
|
||||
)
|
||||
|
||||
+353
-2102
File diff suppressed because it is too large
Load Diff
@@ -1,182 +0,0 @@
|
||||
# engine/ddl.py
|
||||
# Copyright (C) 2009-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Routines to handle CREATE/DROP workflow."""
|
||||
|
||||
from sqlalchemy import engine, schema
|
||||
from sqlalchemy.sql import util as sql_util
|
||||
|
||||
|
||||
class DDLBase(schema.SchemaVisitor):
|
||||
def __init__(self, connection):
|
||||
self.connection = connection
|
||||
|
||||
class SchemaGenerator(DDLBase):
|
||||
def __init__(self, dialect, connection, checkfirst=False, tables=None, **kwargs):
|
||||
super(SchemaGenerator, self).__init__(connection, **kwargs)
|
||||
self.checkfirst = checkfirst
|
||||
self.tables = tables and set(tables) or None
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
self.memo = {}
|
||||
|
||||
def _can_create_table(self, table):
|
||||
self.dialect.validate_identifier(table.name)
|
||||
if table.schema:
|
||||
self.dialect.validate_identifier(table.schema)
|
||||
return not self.checkfirst or \
|
||||
not self.dialect.has_table(self.connection,
|
||||
table.name, schema=table.schema)
|
||||
|
||||
def _can_create_sequence(self, sequence):
|
||||
return self.dialect.supports_sequences and \
|
||||
(
|
||||
(not self.dialect.sequences_optional or
|
||||
not sequence.optional) and
|
||||
(
|
||||
not self.checkfirst or
|
||||
not self.dialect.has_sequence(
|
||||
self.connection,
|
||||
sequence.name,
|
||||
schema=sequence.schema)
|
||||
)
|
||||
)
|
||||
|
||||
def visit_metadata(self, metadata):
|
||||
if self.tables:
|
||||
tables = self.tables
|
||||
else:
|
||||
tables = metadata.tables.values()
|
||||
collection = [t for t in sql_util.sort_tables(tables)
|
||||
if self._can_create_table(t)]
|
||||
seq_coll = [s for s in metadata._sequences.values()
|
||||
if s.column is None and self._can_create_sequence(s)]
|
||||
|
||||
metadata.dispatch.before_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, create_ok=True)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, create_ok=True)
|
||||
|
||||
metadata.dispatch.after_create(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_table(self, table, create_ok=False):
|
||||
if not create_ok and not self._can_create_table(table):
|
||||
return
|
||||
|
||||
table.dispatch.before_create(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(schema.CreateTable(table))
|
||||
|
||||
if hasattr(table, 'indexes'):
|
||||
for index in table.indexes:
|
||||
self.traverse_single(index)
|
||||
|
||||
table.dispatch.after_create(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_sequence(self, sequence, create_ok=False):
|
||||
if not create_ok and not self._can_create_sequence(sequence):
|
||||
return
|
||||
self.connection.execute(schema.CreateSequence(sequence))
|
||||
|
||||
def visit_index(self, index):
|
||||
self.connection.execute(schema.CreateIndex(index))
|
||||
|
||||
|
||||
class SchemaDropper(DDLBase):
|
||||
def __init__(self, dialect, connection, checkfirst=False, tables=None, **kwargs):
|
||||
super(SchemaDropper, self).__init__(connection, **kwargs)
|
||||
self.checkfirst = checkfirst
|
||||
self.tables = tables
|
||||
self.preparer = dialect.identifier_preparer
|
||||
self.dialect = dialect
|
||||
self.memo = {}
|
||||
|
||||
def visit_metadata(self, metadata):
|
||||
if self.tables:
|
||||
tables = self.tables
|
||||
else:
|
||||
tables = metadata.tables.values()
|
||||
collection = [t for t in reversed(sql_util.sort_tables(tables))
|
||||
if self._can_drop_table(t)]
|
||||
seq_coll = [s for s in metadata._sequences.values()
|
||||
if s.column is None and self._can_drop_sequence(s)]
|
||||
|
||||
metadata.dispatch.before_drop(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for table in collection:
|
||||
self.traverse_single(table, drop_ok=True)
|
||||
|
||||
for seq in seq_coll:
|
||||
self.traverse_single(seq, drop_ok=True)
|
||||
|
||||
metadata.dispatch.after_drop(metadata, self.connection,
|
||||
tables=collection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def _can_drop_table(self, table):
|
||||
self.dialect.validate_identifier(table.name)
|
||||
if table.schema:
|
||||
self.dialect.validate_identifier(table.schema)
|
||||
return not self.checkfirst or self.dialect.has_table(self.connection,
|
||||
table.name, schema=table.schema)
|
||||
|
||||
def _can_drop_sequence(self, sequence):
|
||||
return self.dialect.supports_sequences and \
|
||||
((not self.dialect.sequences_optional or
|
||||
not sequence.optional) and
|
||||
(not self.checkfirst or
|
||||
self.dialect.has_sequence(
|
||||
self.connection,
|
||||
sequence.name,
|
||||
schema=sequence.schema))
|
||||
)
|
||||
|
||||
def visit_index(self, index):
|
||||
self.connection.execute(schema.DropIndex(index))
|
||||
|
||||
def visit_table(self, table, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_table(table):
|
||||
return
|
||||
|
||||
table.dispatch.before_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
for column in table.columns:
|
||||
if column.default is not None:
|
||||
self.traverse_single(column.default)
|
||||
|
||||
self.connection.execute(schema.DropTable(table))
|
||||
|
||||
table.dispatch.after_drop(table, self.connection,
|
||||
checkfirst=self.checkfirst,
|
||||
_ddl_runner=self)
|
||||
|
||||
def visit_sequence(self, sequence, drop_ok=False):
|
||||
if not drop_ok and not self._can_drop_sequence(sequence):
|
||||
return
|
||||
self.connection.execute(schema.DropSequence(sequence))
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/default.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -12,19 +12,23 @@ as the base class for their own corresponding classes.
|
||||
|
||||
"""
|
||||
|
||||
import re, random
|
||||
from sqlalchemy.engine import base, reflection
|
||||
from sqlalchemy.sql import compiler, expression
|
||||
from sqlalchemy import exc, types as sqltypes, util, pool, processors
|
||||
import re
|
||||
import random
|
||||
from . import reflection, interfaces, result
|
||||
from ..sql import compiler, expression
|
||||
from .. import types as sqltypes
|
||||
from .. import exc, util, pool, processors
|
||||
import codecs
|
||||
import weakref
|
||||
from .. import event
|
||||
|
||||
AUTOCOMMIT_REGEXP = re.compile(
|
||||
r'\s*(?:UPDATE|INSERT|CREATE|DELETE|DROP|ALTER)',
|
||||
re.I | re.UNICODE)
|
||||
|
||||
|
||||
class DefaultDialect(base.Dialect):
|
||||
|
||||
class DefaultDialect(interfaces.Dialect):
|
||||
"""Default implementation of Dialect"""
|
||||
|
||||
statement_compiler = compiler.SQLCompiler
|
||||
@@ -33,6 +37,10 @@ class DefaultDialect(base.Dialect):
|
||||
preparer = compiler.IdentifierPreparer
|
||||
supports_alter = True
|
||||
|
||||
# the first value we'd get for an autoincrement
|
||||
# column.
|
||||
default_sequence_base = 1
|
||||
|
||||
# most DBAPIs happy with this for execute().
|
||||
# not cx_oracle.
|
||||
execute_sequence_format = tuple
|
||||
@@ -44,27 +52,40 @@ class DefaultDialect(base.Dialect):
|
||||
postfetch_lastrowid = True
|
||||
implicit_returning = False
|
||||
|
||||
supports_right_nested_joins = True
|
||||
|
||||
supports_native_enum = False
|
||||
supports_native_boolean = False
|
||||
|
||||
supports_simple_order_by_label = True
|
||||
|
||||
engine_config_types = util.immutabledict([
|
||||
('convert_unicode', util.bool_or_str('force')),
|
||||
('pool_timeout', int),
|
||||
('echo', util.bool_or_str('debug')),
|
||||
('echo_pool', util.bool_or_str('debug')),
|
||||
('pool_recycle', int),
|
||||
('pool_size', int),
|
||||
('max_overflow', int),
|
||||
('pool_threadlocal', bool),
|
||||
('use_native_unicode', bool),
|
||||
])
|
||||
|
||||
# if the NUMERIC type
|
||||
# returns decimal.Decimal.
|
||||
# *not* the FLOAT type however.
|
||||
supports_native_decimal = False
|
||||
|
||||
# Py3K
|
||||
#supports_unicode_statements = True
|
||||
#supports_unicode_binds = True
|
||||
#returns_unicode_strings = True
|
||||
#description_encoding = None
|
||||
# Py2K
|
||||
supports_unicode_statements = False
|
||||
supports_unicode_binds = False
|
||||
returns_unicode_strings = False
|
||||
description_encoding = 'use_encoding'
|
||||
# end Py2K
|
||||
|
||||
if util.py3k:
|
||||
supports_unicode_statements = True
|
||||
supports_unicode_binds = True
|
||||
returns_unicode_strings = True
|
||||
description_encoding = None
|
||||
else:
|
||||
supports_unicode_statements = False
|
||||
supports_unicode_binds = False
|
||||
returns_unicode_strings = False
|
||||
description_encoding = 'use_encoding'
|
||||
|
||||
name = 'default'
|
||||
|
||||
@@ -86,6 +107,7 @@ class DefaultDialect(base.Dialect):
|
||||
default_paramstyle = 'named'
|
||||
supports_default_values = False
|
||||
supports_empty_insert = True
|
||||
supports_multivalues_insert = False
|
||||
|
||||
server_version_info = None
|
||||
|
||||
@@ -98,27 +120,20 @@ class DefaultDialect(base.Dialect):
|
||||
|
||||
reflection_options = ()
|
||||
|
||||
def __init__(self, convert_unicode=False, assert_unicode=False,
|
||||
def __init__(self, convert_unicode=False,
|
||||
encoding='utf-8', paramstyle=None, dbapi=None,
|
||||
implicit_returning=None,
|
||||
supports_right_nested_joins=None,
|
||||
case_sensitive=True,
|
||||
supports_native_boolean=None,
|
||||
label_length=None, **kwargs):
|
||||
|
||||
if not getattr(self, 'ported_sqla_06', True):
|
||||
util.warn(
|
||||
"The %s dialect is not yet ported to SQLAlchemy 0.6/0.7" %
|
||||
"The %s dialect is not yet ported to the 0.6 format" %
|
||||
self.name)
|
||||
|
||||
self.convert_unicode = convert_unicode
|
||||
if assert_unicode:
|
||||
util.warn_deprecated(
|
||||
"assert_unicode is deprecated. "
|
||||
"SQLAlchemy emits a warning in all cases where it "
|
||||
"would otherwise like to encode a Python unicode object "
|
||||
"into a specific encoding but a plain bytestring is "
|
||||
"received. "
|
||||
"This does *not* apply to DBAPIs that coerce Unicode "
|
||||
"natively.")
|
||||
|
||||
self.encoding = encoding
|
||||
self.positional = False
|
||||
self._ischema = None
|
||||
@@ -134,6 +149,11 @@ class DefaultDialect(base.Dialect):
|
||||
self.positional = self.paramstyle in ('qmark', 'format', 'numeric')
|
||||
self.identifier_preparer = self.preparer(self)
|
||||
self.type_compiler = self.type_compiler(self)
|
||||
if supports_right_nested_joins is not None:
|
||||
self.supports_right_nested_joins = supports_right_nested_joins
|
||||
if supports_native_boolean is not None:
|
||||
self.supports_native_boolean = supports_native_boolean
|
||||
self.case_sensitive = case_sensitive
|
||||
|
||||
if label_length and label_length > self.max_identifier_length:
|
||||
raise exc.ArgumentError(
|
||||
@@ -143,16 +163,19 @@ class DefaultDialect(base.Dialect):
|
||||
self.label_length = label_length
|
||||
|
||||
if self.description_encoding == 'use_encoding':
|
||||
self._description_decoder = processors.to_unicode_processor_factory(
|
||||
self._description_decoder = \
|
||||
processors.to_unicode_processor_factory(
|
||||
encoding
|
||||
)
|
||||
elif self.description_encoding is not None:
|
||||
self._description_decoder = processors.to_unicode_processor_factory(
|
||||
self._description_decoder = \
|
||||
processors.to_unicode_processor_factory(
|
||||
self.description_encoding
|
||||
)
|
||||
self._encoder = codecs.getencoder(self.encoding)
|
||||
self._decoder = processors.to_unicode_processor_factory(self.encoding)
|
||||
|
||||
|
||||
@util.memoized_property
|
||||
def _type_memos(self):
|
||||
return weakref.WeakKeyDictionary()
|
||||
@@ -185,6 +208,10 @@ class DefaultDialect(base.Dialect):
|
||||
|
||||
self.returns_unicode_strings = self._check_unicode_returns(connection)
|
||||
|
||||
if self.description_encoding is not None and \
|
||||
self._check_unicode_description(connection):
|
||||
self._description_decoder = self.description_encoding = None
|
||||
|
||||
self.do_rollback(connection.connection)
|
||||
|
||||
def on_connect(self):
|
||||
@@ -202,14 +229,11 @@ class DefaultDialect(base.Dialect):
|
||||
return None
|
||||
|
||||
def _check_unicode_returns(self, connection):
|
||||
# Py2K
|
||||
if self.supports_unicode_statements:
|
||||
cast_to = unicode
|
||||
if util.py2k and not self.supports_unicode_statements:
|
||||
cast_to = util.binary_type
|
||||
else:
|
||||
cast_to = str
|
||||
# end Py2K
|
||||
# Py3K
|
||||
#cast_to = str
|
||||
cast_to = util.text_type
|
||||
|
||||
def check_unicode(formatstr, type_):
|
||||
cursor = connection.connection.cursor()
|
||||
try:
|
||||
@@ -217,16 +241,17 @@ class DefaultDialect(base.Dialect):
|
||||
cursor.execute(
|
||||
cast_to(
|
||||
expression.select(
|
||||
[expression.cast(
|
||||
expression.literal_column(
|
||||
"'test %s returns'" % formatstr), type_)
|
||||
[expression.cast(
|
||||
expression.literal_column(
|
||||
"'test %s returns'" % formatstr),
|
||||
type_)
|
||||
]).compile(dialect=self)
|
||||
)
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
|
||||
return isinstance(row[0], unicode)
|
||||
except self.dbapi.Error, de:
|
||||
return isinstance(row[0], util.text_type)
|
||||
except self.dbapi.Error as de:
|
||||
util.warn("Exception attempting to "
|
||||
"detect unicode returns: %r" % de)
|
||||
return False
|
||||
@@ -244,18 +269,41 @@ class DefaultDialect(base.Dialect):
|
||||
else:
|
||||
return unicode_for_varchar
|
||||
|
||||
def _check_unicode_description(self, connection):
|
||||
# all DBAPIs on Py2K return cursor.description as encoded,
|
||||
# until pypy2.1beta2 with sqlite, so let's just check it -
|
||||
# it's likely others will start doing this too in Py2k.
|
||||
|
||||
if util.py2k and not self.supports_unicode_statements:
|
||||
cast_to = util.binary_type
|
||||
else:
|
||||
cast_to = util.text_type
|
||||
|
||||
cursor = connection.connection.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
cast_to(
|
||||
expression.select([
|
||||
expression.literal_column("'x'").label("some_label")
|
||||
]).compile(dialect=self)
|
||||
)
|
||||
)
|
||||
return isinstance(cursor.description[0][0], util.text_type)
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
def type_descriptor(self, typeobj):
|
||||
"""Provide a database-specific ``TypeEngine`` object, given
|
||||
"""Provide a database-specific :class:`.TypeEngine` object, given
|
||||
the generic object which comes from the types module.
|
||||
|
||||
This method looks for a dictionary called
|
||||
``colspecs`` as a class or instance-level variable,
|
||||
and passes on to ``types.adapt_type()``.
|
||||
and passes on to :func:`.types.adapt_type`.
|
||||
|
||||
"""
|
||||
return sqltypes.adapt_type(typeobj, self.colspecs)
|
||||
|
||||
def reflecttable(self, connection, table, include_columns, exclude_columns=None):
|
||||
def reflecttable(self, connection, table, include_columns, exclude_columns):
|
||||
insp = reflection.Inspector.from_engine(connection)
|
||||
return insp.reflecttable(table, include_columns, exclude_columns)
|
||||
|
||||
@@ -285,26 +333,35 @@ class DefaultDialect(base.Dialect):
|
||||
opts.update(url.query)
|
||||
return [[], opts]
|
||||
|
||||
def do_begin(self, connection):
|
||||
"""Implementations might want to put logic here for turning
|
||||
autocommit on/off, etc.
|
||||
"""
|
||||
def set_engine_execution_options(self, engine, opts):
|
||||
if 'isolation_level' in opts:
|
||||
isolation_level = opts['isolation_level']
|
||||
@event.listens_for(engine, "engine_connect")
|
||||
def set_isolation(connection, branch):
|
||||
if not branch:
|
||||
self._set_connection_isolation(connection, isolation_level)
|
||||
|
||||
def set_connection_execution_options(self, connection, opts):
|
||||
if 'isolation_level' in opts:
|
||||
self._set_connection_isolation(connection, opts['isolation_level'])
|
||||
|
||||
def _set_connection_isolation(self, connection, level):
|
||||
self.set_isolation_level(connection.connection, level)
|
||||
connection.connection._connection_record.\
|
||||
finalize_callback.append(self.reset_isolation_level)
|
||||
|
||||
|
||||
def do_begin(self, dbapi_connection):
|
||||
pass
|
||||
|
||||
def do_rollback(self, connection):
|
||||
"""Implementations might want to put logic here for turning
|
||||
autocommit on/off, etc.
|
||||
"""
|
||||
def do_rollback(self, dbapi_connection):
|
||||
dbapi_connection.rollback()
|
||||
|
||||
connection.rollback()
|
||||
def do_commit(self, dbapi_connection):
|
||||
dbapi_connection.commit()
|
||||
|
||||
def do_commit(self, connection):
|
||||
"""Implementations might want to put logic here for turning
|
||||
autocommit on/off, etc.
|
||||
"""
|
||||
|
||||
connection.commit()
|
||||
def do_close(self, dbapi_connection):
|
||||
dbapi_connection.close()
|
||||
|
||||
def create_xid(self):
|
||||
"""Create a random two-phase transaction ID.
|
||||
@@ -342,7 +399,8 @@ class DefaultDialect(base.Dialect):
|
||||
# the configured default of this dialect.
|
||||
self.set_isolation_level(dbapi_conn, self.default_isolation_level)
|
||||
|
||||
class DefaultExecutionContext(base.ExecutionContext):
|
||||
|
||||
class DefaultExecutionContext(interfaces.ExecutionContext):
|
||||
isinsert = False
|
||||
isupdate = False
|
||||
isdelete = False
|
||||
@@ -353,6 +411,7 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
statement = None
|
||||
postfetch_cols = None
|
||||
prefetch_cols = None
|
||||
returning_cols = None
|
||||
_is_implicit_returning = False
|
||||
_is_explicit_returning = False
|
||||
|
||||
@@ -379,10 +438,10 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
self.execution_options.update(connection._execution_options)
|
||||
|
||||
if not dialect.supports_unicode_statements:
|
||||
self.unicode_statement = unicode(compiled)
|
||||
self.unicode_statement = util.text_type(compiled)
|
||||
self.statement = dialect._encoder(self.unicode_statement)[0]
|
||||
else:
|
||||
self.statement = self.unicode_statement = unicode(compiled)
|
||||
self.statement = self.unicode_statement = util.text_type(compiled)
|
||||
|
||||
self.cursor = self.create_cursor()
|
||||
self.compiled_parameters = []
|
||||
@@ -395,7 +454,8 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _init_compiled(cls, dialect, connection, dbapi_connection, compiled, parameters):
|
||||
def _init_compiled(cls, dialect, connection, dbapi_connection,
|
||||
compiled, parameters):
|
||||
"""Initialize execution context for a Compiled construct."""
|
||||
|
||||
self = cls.__new__(cls)
|
||||
@@ -419,9 +479,10 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
|
||||
self.result_map = compiled.result_map
|
||||
|
||||
self.unicode_statement = unicode(compiled)
|
||||
self.unicode_statement = util.text_type(compiled)
|
||||
if not dialect.supports_unicode_statements:
|
||||
self.statement = self.unicode_statement.encode(self.dialect.encoding)
|
||||
self.statement = self.unicode_statement.encode(
|
||||
self.dialect.encoding)
|
||||
else:
|
||||
self.statement = self.unicode_statement
|
||||
|
||||
@@ -430,16 +491,16 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
self.isdelete = compiled.isdelete
|
||||
|
||||
if self.isinsert or self.isupdate or self.isdelete:
|
||||
self._is_explicit_returning = compiled.statement._returning
|
||||
self._is_implicit_returning = compiled.returning and \
|
||||
not compiled.statement._returning
|
||||
self._is_explicit_returning = bool(compiled.statement._returning)
|
||||
self._is_implicit_returning = bool(compiled.returning and \
|
||||
not compiled.statement._returning)
|
||||
|
||||
if not parameters:
|
||||
self.compiled_parameters = [compiled.construct_params()]
|
||||
else:
|
||||
self.compiled_parameters = \
|
||||
[compiled.construct_params(m, _group_number=grp) for
|
||||
grp,m in enumerate(parameters)]
|
||||
grp, m in enumerate(parameters)]
|
||||
|
||||
self.executemany = len(parameters) > 1
|
||||
|
||||
@@ -447,6 +508,7 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
if self.isinsert or self.isupdate:
|
||||
self.postfetch_cols = self.compiled.postfetch
|
||||
self.prefetch_cols = self.compiled.prefetch
|
||||
self.returning_cols = self.compiled.returning
|
||||
self.__process_defaults()
|
||||
|
||||
processors = compiled._bind_processors
|
||||
@@ -474,7 +536,8 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
param[dialect._encoder(key)[0]] = \
|
||||
processors[key](compiled_params[key])
|
||||
else:
|
||||
param[dialect._encoder(key)[0]] = compiled_params[key]
|
||||
param[dialect._encoder(key)[0]] = \
|
||||
compiled_params[key]
|
||||
else:
|
||||
for key in compiled_params:
|
||||
if key in processors:
|
||||
@@ -487,7 +550,8 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _init_statement(cls, dialect, connection, dbapi_connection, statement, parameters):
|
||||
def _init_statement(cls, dialect, connection, dbapi_connection,
|
||||
statement, parameters):
|
||||
"""Initialize execution context for a string SQL statement."""
|
||||
|
||||
self = cls.__new__(cls)
|
||||
@@ -510,7 +574,7 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
if dialect.supports_unicode_statements:
|
||||
self.parameters = parameters
|
||||
else:
|
||||
self.parameters= [
|
||||
self.parameters = [
|
||||
dict((dialect._encoder(k)[0], d[k]) for k in d)
|
||||
for d in parameters
|
||||
] or [{}]
|
||||
@@ -520,7 +584,8 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
|
||||
self.executemany = len(parameters) > 1
|
||||
|
||||
if not dialect.supports_unicode_statements and isinstance(statement, unicode):
|
||||
if not dialect.supports_unicode_statements and \
|
||||
isinstance(statement, util.text_type):
|
||||
self.unicode_statement = statement
|
||||
self.statement = dialect._encoder(statement)[0]
|
||||
else:
|
||||
@@ -574,8 +639,8 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
"""
|
||||
|
||||
conn = self.root_connection
|
||||
if isinstance(stmt, unicode) and \
|
||||
not self.dialect.supports_unicode_statements:
|
||||
if isinstance(stmt, util.text_type) and \
|
||||
not self.dialect.supports_unicode_statements:
|
||||
stmt = self.dialect._encoder(stmt)[0]
|
||||
|
||||
if self.dialect.positional:
|
||||
@@ -583,7 +648,7 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
else:
|
||||
default_params = {}
|
||||
|
||||
conn._cursor_execute(self.cursor, stmt, default_params)
|
||||
conn._cursor_execute(self.cursor, stmt, default_params, context=self)
|
||||
r = self.cursor.fetchone()[0]
|
||||
if type_ is not None:
|
||||
# apply type post processors to the result
|
||||
@@ -611,6 +676,16 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
def post_exec(self):
|
||||
pass
|
||||
|
||||
def get_result_processor(self, type_, colname, coltype):
|
||||
"""Return a 'result processor' for a given type as present in
|
||||
cursor.description.
|
||||
|
||||
This has a default implementation that dialects can override
|
||||
for context-sensitive result type handling.
|
||||
|
||||
"""
|
||||
return type_._cached_result_processor(self.dialect, coltype)
|
||||
|
||||
def get_lastrowid(self):
|
||||
"""return self.cursor.lastrowid, or equivalent, after an INSERT.
|
||||
|
||||
@@ -643,7 +718,7 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
pass
|
||||
|
||||
def get_result_proxy(self):
|
||||
return base.ResultProxy(self)
|
||||
return result.ResultProxy(self)
|
||||
|
||||
@property
|
||||
def rowcount(self):
|
||||
@@ -657,22 +732,24 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
|
||||
def post_insert(self):
|
||||
if not self._is_implicit_returning and \
|
||||
not self._is_explicit_returning and \
|
||||
not self.compiled.inline and \
|
||||
self.dialect.postfetch_lastrowid and \
|
||||
(not self.inserted_primary_key or \
|
||||
None in self.inserted_primary_key):
|
||||
|
||||
table = self.compiled.statement.table
|
||||
lastrowid = self.get_lastrowid()
|
||||
|
||||
autoinc_col = table._autoincrement_column
|
||||
if autoinc_col is not None:
|
||||
# apply type post processors to the lastrowid
|
||||
proc = autoinc_col.type._cached_result_processor(self.dialect, None)
|
||||
proc = autoinc_col.type._cached_result_processor(
|
||||
self.dialect, None)
|
||||
if proc is not None:
|
||||
lastrowid = proc(lastrowid)
|
||||
|
||||
self.inserted_primary_key = [
|
||||
c is autoinc_col and lastrowid or v
|
||||
lastrowid if c is autoinc_col else v
|
||||
for c, v in zip(
|
||||
table.primary_key,
|
||||
self.inserted_primary_key)
|
||||
@@ -690,6 +767,11 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
ipk.append(row[c])
|
||||
|
||||
self.inserted_primary_key = ipk
|
||||
self.returned_defaults = row
|
||||
|
||||
def _fetch_implicit_update_returning(self, resultproxy):
|
||||
row = resultproxy.fetchone()
|
||||
self.returned_defaults = row
|
||||
|
||||
def lastrow_has_defaults(self):
|
||||
return (self.isinsert or self.isupdate) and \
|
||||
@@ -716,28 +798,34 @@ class DefaultExecutionContext(base.ExecutionContext):
|
||||
inputsizes = []
|
||||
for key in self.compiled.positiontup:
|
||||
typeengine = types[key]
|
||||
dbtype = typeengine.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi)
|
||||
if dbtype is not None and (not exclude_types or dbtype not in exclude_types):
|
||||
dbtype = typeengine.dialect_impl(self.dialect).\
|
||||
get_dbapi_type(self.dialect.dbapi)
|
||||
if dbtype is not None and \
|
||||
(not exclude_types or dbtype not in exclude_types):
|
||||
inputsizes.append(dbtype)
|
||||
try:
|
||||
self.cursor.setinputsizes(*inputsizes)
|
||||
except Exception, e:
|
||||
self.root_connection._handle_dbapi_exception(e, None, None, None, self)
|
||||
raise
|
||||
except Exception as e:
|
||||
self.root_connection._handle_dbapi_exception(
|
||||
e, None, None, None, self)
|
||||
else:
|
||||
inputsizes = {}
|
||||
for key in self.compiled.bind_names.values():
|
||||
typeengine = types[key]
|
||||
dbtype = typeengine.dialect_impl(self.dialect).get_dbapi_type(self.dialect.dbapi)
|
||||
if dbtype is not None and (not exclude_types or dbtype not in exclude_types):
|
||||
dbtype = typeengine.dialect_impl(self.dialect).\
|
||||
get_dbapi_type(self.dialect.dbapi)
|
||||
if dbtype is not None and \
|
||||
(not exclude_types or dbtype not in exclude_types):
|
||||
if translate:
|
||||
key = translate.get(key, key)
|
||||
inputsizes[self.dialect._encoder(key)[0]] = dbtype
|
||||
if not self.dialect.supports_unicode_binds:
|
||||
key = self.dialect._encoder(key)[0]
|
||||
inputsizes[key] = dbtype
|
||||
try:
|
||||
self.cursor.setinputsizes(**inputsizes)
|
||||
except Exception, e:
|
||||
self.root_connection._handle_dbapi_exception(e, None, None, None, self)
|
||||
raise
|
||||
except Exception as e:
|
||||
self.root_connection._handle_dbapi_exception(
|
||||
e, None, None, None, self)
|
||||
|
||||
def _exec_default(self, default, type_):
|
||||
if default.is_sequence:
|
||||
|
||||
@@ -0,0 +1,846 @@
|
||||
# engine/interfaces.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Define core interfaces used by the engine system."""
|
||||
|
||||
from .. import util, event
|
||||
|
||||
# backwards compat
|
||||
from ..sql.compiler import Compiled, TypeCompiler
|
||||
|
||||
class Dialect(object):
|
||||
"""Define the behavior of a specific database and DB-API combination.
|
||||
|
||||
Any aspect of metadata definition, SQL query generation,
|
||||
execution, result-set handling, or anything else which varies
|
||||
between databases is defined under the general category of the
|
||||
Dialect. The Dialect acts as a factory for other
|
||||
database-specific object implementations including
|
||||
ExecutionContext, Compiled, DefaultGenerator, and TypeEngine.
|
||||
|
||||
All Dialects implement the following attributes:
|
||||
|
||||
name
|
||||
identifying name for the dialect from a DBAPI-neutral point of view
|
||||
(i.e. 'sqlite')
|
||||
|
||||
driver
|
||||
identifying name for the dialect's DBAPI
|
||||
|
||||
positional
|
||||
True if the paramstyle for this Dialect is positional.
|
||||
|
||||
paramstyle
|
||||
the paramstyle to be used (some DB-APIs support multiple
|
||||
paramstyles).
|
||||
|
||||
convert_unicode
|
||||
True if Unicode conversion should be applied to all ``str``
|
||||
types.
|
||||
|
||||
encoding
|
||||
type of encoding to use for unicode, usually defaults to
|
||||
'utf-8'.
|
||||
|
||||
statement_compiler
|
||||
a :class:`.Compiled` class used to compile SQL statements
|
||||
|
||||
ddl_compiler
|
||||
a :class:`.Compiled` class used to compile DDL statements
|
||||
|
||||
server_version_info
|
||||
a tuple containing a version number for the DB backend in use.
|
||||
This value is only available for supporting dialects, and is
|
||||
typically populated during the initial connection to the database.
|
||||
|
||||
default_schema_name
|
||||
the name of the default schema. This value is only available for
|
||||
supporting dialects, and is typically populated during the
|
||||
initial connection to the database.
|
||||
|
||||
execution_ctx_cls
|
||||
a :class:`.ExecutionContext` class used to handle statement execution
|
||||
|
||||
execute_sequence_format
|
||||
either the 'tuple' or 'list' type, depending on what cursor.execute()
|
||||
accepts for the second argument (they vary).
|
||||
|
||||
preparer
|
||||
a :class:`~sqlalchemy.sql.compiler.IdentifierPreparer` class used to
|
||||
quote identifiers.
|
||||
|
||||
supports_alter
|
||||
``True`` if the database supports ``ALTER TABLE``.
|
||||
|
||||
max_identifier_length
|
||||
The maximum length of identifier names.
|
||||
|
||||
supports_unicode_statements
|
||||
Indicate whether the DB-API can receive SQL statements as Python
|
||||
unicode strings
|
||||
|
||||
supports_unicode_binds
|
||||
Indicate whether the DB-API can receive string bind parameters
|
||||
as Python unicode strings
|
||||
|
||||
supports_sane_rowcount
|
||||
Indicate whether the dialect properly implements rowcount for
|
||||
``UPDATE`` and ``DELETE`` statements.
|
||||
|
||||
supports_sane_multi_rowcount
|
||||
Indicate whether the dialect properly implements rowcount for
|
||||
``UPDATE`` and ``DELETE`` statements when executed via
|
||||
executemany.
|
||||
|
||||
preexecute_autoincrement_sequences
|
||||
True if 'implicit' primary key functions must be executed separately
|
||||
in order to get their value. This is currently oriented towards
|
||||
Postgresql.
|
||||
|
||||
implicit_returning
|
||||
use RETURNING or equivalent during INSERT execution in order to load
|
||||
newly generated primary keys and other column defaults in one execution,
|
||||
which are then available via inserted_primary_key.
|
||||
If an insert statement has returning() specified explicitly,
|
||||
the "implicit" functionality is not used and inserted_primary_key
|
||||
will not be available.
|
||||
|
||||
dbapi_type_map
|
||||
A mapping of DB-API type objects present in this Dialect's
|
||||
DB-API implementation mapped to TypeEngine implementations used
|
||||
by the dialect.
|
||||
|
||||
This is used to apply types to result sets based on the DB-API
|
||||
types present in cursor.description; it only takes effect for
|
||||
result sets against textual statements where no explicit
|
||||
typemap was present.
|
||||
|
||||
colspecs
|
||||
A dictionary of TypeEngine classes from sqlalchemy.types mapped
|
||||
to subclasses that are specific to the dialect class. This
|
||||
dictionary is class-level only and is not accessed from the
|
||||
dialect instance itself.
|
||||
|
||||
supports_default_values
|
||||
Indicates if the construct ``INSERT INTO tablename DEFAULT
|
||||
VALUES`` is supported
|
||||
|
||||
supports_sequences
|
||||
Indicates if the dialect supports CREATE SEQUENCE or similar.
|
||||
|
||||
sequences_optional
|
||||
If True, indicates if the "optional" flag on the Sequence() construct
|
||||
should signal to not generate a CREATE SEQUENCE. Applies only to
|
||||
dialects that support sequences. Currently used only to allow Postgresql
|
||||
SERIAL to be used on a column that specifies Sequence() for usage on
|
||||
other backends.
|
||||
|
||||
supports_native_enum
|
||||
Indicates if the dialect supports a native ENUM construct.
|
||||
This will prevent types.Enum from generating a CHECK
|
||||
constraint when that type is used.
|
||||
|
||||
supports_native_boolean
|
||||
Indicates if the dialect supports a native boolean construct.
|
||||
This will prevent types.Boolean from generating a CHECK
|
||||
constraint when that type is used.
|
||||
|
||||
"""
|
||||
|
||||
def create_connect_args(self, url):
|
||||
"""Build DB-API compatible connection arguments.
|
||||
|
||||
Given a :class:`~sqlalchemy.engine.url.URL` object, returns a tuple
|
||||
consisting of a `*args`/`**kwargs` suitable to send directly
|
||||
to the dbapi's connect function.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def type_descriptor(cls, typeobj):
|
||||
"""Transform a generic type to a dialect-specific type.
|
||||
|
||||
Dialect classes will usually use the
|
||||
:func:`.types.adapt_type` function in the types module to
|
||||
accomplish this.
|
||||
|
||||
The returned result is cached *per dialect class* so can
|
||||
contain no dialect-instance state.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def initialize(self, connection):
|
||||
"""Called during strategized creation of the dialect with a
|
||||
connection.
|
||||
|
||||
Allows dialects to configure options based on server version info or
|
||||
other properties.
|
||||
|
||||
The connection passed here is a SQLAlchemy Connection object,
|
||||
with full capabilities.
|
||||
|
||||
The initalize() method of the base dialect should be called via
|
||||
super().
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def reflecttable(self, connection, table, include_columns, exclude_columns):
|
||||
"""Load table description from the database.
|
||||
|
||||
Given a :class:`.Connection` and a
|
||||
:class:`~sqlalchemy.schema.Table` object, reflect its columns and
|
||||
properties from the database.
|
||||
|
||||
The implementation of this method is provided by
|
||||
:meth:`.DefaultDialect.reflecttable`, which makes use of
|
||||
:class:`.Inspector` to retrieve column information.
|
||||
|
||||
Dialects should **not** seek to implement this method, and should
|
||||
instead implement individual schema inspection operations such as
|
||||
:meth:`.Dialect.get_columns`, :meth:`.Dialect.get_pk_constraint`,
|
||||
etc.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_columns(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about columns in `table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name`, and an optional string `schema`, return column
|
||||
information as a list of dictionaries with these keys:
|
||||
|
||||
name
|
||||
the column's name
|
||||
|
||||
type
|
||||
[sqlalchemy.types#TypeEngine]
|
||||
|
||||
nullable
|
||||
boolean
|
||||
|
||||
default
|
||||
the column's default value
|
||||
|
||||
autoincrement
|
||||
boolean
|
||||
|
||||
sequence
|
||||
a dictionary of the form
|
||||
{'name' : str, 'start' :int, 'increment': int}
|
||||
|
||||
Additional column attributes may be present.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_primary_keys(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about primary keys in `table_name`.
|
||||
|
||||
|
||||
Deprecated. This method is only called by the default
|
||||
implementation of :meth:`.Dialect.get_pk_constraint`. Dialects should
|
||||
instead implement the :meth:`.Dialect.get_pk_constraint` method directly.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_pk_constraint(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about the primary key constraint on
|
||||
table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name`, and an optional string `schema`, return primary
|
||||
key information as a dictionary with these keys:
|
||||
|
||||
constrained_columns
|
||||
a list of column names that make up the primary key
|
||||
|
||||
name
|
||||
optional name of the primary key constraint.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_foreign_keys(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about foreign_keys in `table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name`, and an optional string `schema`, return foreign
|
||||
key information as a list of dicts with these keys:
|
||||
|
||||
name
|
||||
the constraint's name
|
||||
|
||||
constrained_columns
|
||||
a list of column names that make up the foreign key
|
||||
|
||||
referred_schema
|
||||
the name of the referred schema
|
||||
|
||||
referred_table
|
||||
the name of the referred table
|
||||
|
||||
referred_columns
|
||||
a list of column names in the referred table that correspond to
|
||||
constrained_columns
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_table_names(self, connection, schema=None, **kw):
|
||||
"""Return a list of table names for `schema`."""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def get_view_names(self, connection, schema=None, **kw):
|
||||
"""Return a list of all view names available in the database.
|
||||
|
||||
schema:
|
||||
Optional, retrieve names from a non-default schema.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_view_definition(self, connection, view_name, schema=None, **kw):
|
||||
"""Return view definition.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`view_name`, and an optional string `schema`, return the view
|
||||
definition.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_indexes(self, connection, table_name, schema=None, **kw):
|
||||
"""Return information about indexes in `table_name`.
|
||||
|
||||
Given a :class:`.Connection`, a string
|
||||
`table_name` and an optional string `schema`, return index
|
||||
information as a list of dictionaries with these keys:
|
||||
|
||||
name
|
||||
the index's name
|
||||
|
||||
column_names
|
||||
list of column names in order
|
||||
|
||||
unique
|
||||
boolean
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_unique_constraints(self, table_name, schema=None, **kw):
|
||||
"""Return information about unique constraints in `table_name`.
|
||||
|
||||
Given a string `table_name` and an optional string `schema`, return
|
||||
unique constraint information as a list of dicts with these keys:
|
||||
|
||||
name
|
||||
the unique constraint's name
|
||||
|
||||
column_names
|
||||
list of column names in order
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_unique_constraints() method.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def normalize_name(self, name):
|
||||
"""convert the given name to lowercase if it is detected as
|
||||
case insensitive.
|
||||
|
||||
this method is only used if the dialect defines
|
||||
requires_name_normalize=True.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def denormalize_name(self, name):
|
||||
"""convert the given name to a case insensitive identifier
|
||||
for the backend if it is an all-lowercase name.
|
||||
|
||||
this method is only used if the dialect defines
|
||||
requires_name_normalize=True.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_table(self, connection, table_name, schema=None):
|
||||
"""Check the existence of a particular table in the database.
|
||||
|
||||
Given a :class:`.Connection` object and a string
|
||||
`table_name`, return True if the given table (possibly within
|
||||
the specified `schema`) exists in the database, False
|
||||
otherwise.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_sequence(self, connection, sequence_name, schema=None):
|
||||
"""Check the existence of a particular sequence in the database.
|
||||
|
||||
Given a :class:`.Connection` object and a string
|
||||
`sequence_name`, return True if the given sequence exists in
|
||||
the database, False otherwise.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_server_version_info(self, connection):
|
||||
"""Retrieve the server version info from the given connection.
|
||||
|
||||
This is used by the default implementation to populate the
|
||||
"server_version_info" attribute and is called exactly
|
||||
once upon first connect.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def _get_default_schema_name(self, connection):
|
||||
"""Return the string name of the currently selected schema from
|
||||
the given connection.
|
||||
|
||||
This is used by the default implementation to populate the
|
||||
"default_schema_name" attribute and is called exactly
|
||||
once upon first connect.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_begin(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.begin()``, given a
|
||||
DB-API connection.
|
||||
|
||||
The DBAPI has no dedicated "begin" method and it is expected
|
||||
that transactions are implicit. This hook is provided for those
|
||||
DBAPIs that might need additional help in this area.
|
||||
|
||||
Note that :meth:`.Dialect.do_begin` is not called unless a
|
||||
:class:`.Transaction` object is in use. The
|
||||
:meth:`.Dialect.do_autocommit`
|
||||
hook is provided for DBAPIs that need some extra commands emitted
|
||||
after a commit in order to enter the next transaction, when the
|
||||
SQLAlchemy :class:`.Connection` is used in it's default "autocommit"
|
||||
mode.
|
||||
|
||||
:param dbapi_connection: a DBAPI connection, typically
|
||||
proxied within a :class:`.ConnectionFairy`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_rollback(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.rollback()``, given
|
||||
a DB-API connection.
|
||||
|
||||
:param dbapi_connection: a DBAPI connection, typically
|
||||
proxied within a :class:`.ConnectionFairy`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def do_commit(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.commit()``, given a
|
||||
DB-API connection.
|
||||
|
||||
:param dbapi_connection: a DBAPI connection, typically
|
||||
proxied within a :class:`.ConnectionFairy`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_close(self, dbapi_connection):
|
||||
"""Provide an implementation of ``connection.close()``, given a DBAPI
|
||||
connection.
|
||||
|
||||
This hook is called by the :class:`.Pool` when a connection has been
|
||||
detached from the pool, or is being returned beyond the normal
|
||||
capacity of the pool.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_xid(self):
|
||||
"""Create a two-phase transaction ID.
|
||||
|
||||
This id will be passed to do_begin_twophase(),
|
||||
do_rollback_twophase(), do_commit_twophase(). Its format is
|
||||
unspecified.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_savepoint(self, connection, name):
|
||||
"""Create a savepoint with the given name.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param name: savepoint name.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_rollback_to_savepoint(self, connection, name):
|
||||
"""Rollback a connection to the named savepoint.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param name: savepoint name.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_release_savepoint(self, connection, name):
|
||||
"""Release the named savepoint on a connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param name: savepoint name.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_begin_twophase(self, connection, xid):
|
||||
"""Begin a two phase transaction on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_prepare_twophase(self, connection, xid):
|
||||
"""Prepare a two phase transaction on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_rollback_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
"""Rollback a two phase transaction on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
:param is_prepared: whether or not
|
||||
:meth:`.TwoPhaseTransaction.prepare` was called.
|
||||
:param recover: if the recover flag was passed.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_commit_twophase(self, connection, xid, is_prepared=True,
|
||||
recover=False):
|
||||
"""Commit a two phase transaction on the given connection.
|
||||
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
:param xid: xid
|
||||
:param is_prepared: whether or not
|
||||
:meth:`.TwoPhaseTransaction.prepare` was called.
|
||||
:param recover: if the recover flag was passed.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_recover_twophase(self, connection):
|
||||
"""Recover list of uncommited prepared two phase transaction
|
||||
identifiers on the given connection.
|
||||
|
||||
:param connection: a :class:`.Connection`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_executemany(self, cursor, statement, parameters, context=None):
|
||||
"""Provide an implementation of ``cursor.executemany(statement,
|
||||
parameters)``."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_execute(self, cursor, statement, parameters, context=None):
|
||||
"""Provide an implementation of ``cursor.execute(statement,
|
||||
parameters)``."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def do_execute_no_params(self, cursor, statement, parameters,
|
||||
context=None):
|
||||
"""Provide an implementation of ``cursor.execute(statement)``.
|
||||
|
||||
The parameter collection should not be sent.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_disconnect(self, e, connection, cursor):
|
||||
"""Return True if the given DB-API error indicates an invalid
|
||||
connection"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def connect(self):
|
||||
"""return a callable which sets up a newly created DBAPI connection.
|
||||
|
||||
The callable accepts a single argument "conn" which is the
|
||||
DBAPI connection itself. It has no return value.
|
||||
|
||||
This is used to set dialect-wide per-connection options such as
|
||||
isolation modes, unicode modes, etc.
|
||||
|
||||
If a callable is returned, it will be assembled into a pool listener
|
||||
that receives the direct DBAPI connection, with all wrappers removed.
|
||||
|
||||
If None is returned, no listener will be generated.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def reset_isolation_level(self, dbapi_conn):
|
||||
"""Given a DBAPI connection, revert its isolation to the default."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_isolation_level(self, dbapi_conn, level):
|
||||
"""Given a DBAPI connection, set its isolation level."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_isolation_level(self, dbapi_conn):
|
||||
"""Given a DBAPI connection, return its isolation level."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ExecutionContext(object):
|
||||
"""A messenger object for a Dialect that corresponds to a single
|
||||
execution.
|
||||
|
||||
ExecutionContext should have these data members:
|
||||
|
||||
connection
|
||||
Connection object which can be freely used by default value
|
||||
generators to execute SQL. This Connection should reference the
|
||||
same underlying connection/transactional resources of
|
||||
root_connection.
|
||||
|
||||
root_connection
|
||||
Connection object which is the source of this ExecutionContext. This
|
||||
Connection may have close_with_result=True set, in which case it can
|
||||
only be used once.
|
||||
|
||||
dialect
|
||||
dialect which created this ExecutionContext.
|
||||
|
||||
cursor
|
||||
DB-API cursor procured from the connection,
|
||||
|
||||
compiled
|
||||
if passed to constructor, sqlalchemy.engine.base.Compiled object
|
||||
being executed,
|
||||
|
||||
statement
|
||||
string version of the statement to be executed. Is either
|
||||
passed to the constructor, or must be created from the
|
||||
sql.Compiled object by the time pre_exec() has completed.
|
||||
|
||||
parameters
|
||||
bind parameters passed to the execute() method. For compiled
|
||||
statements, this is a dictionary or list of dictionaries. For
|
||||
textual statements, it should be in a format suitable for the
|
||||
dialect's paramstyle (i.e. dict or list of dicts for non
|
||||
positional, list or list of lists/tuples for positional).
|
||||
|
||||
isinsert
|
||||
True if the statement is an INSERT.
|
||||
|
||||
isupdate
|
||||
True if the statement is an UPDATE.
|
||||
|
||||
should_autocommit
|
||||
True if the statement is a "committable" statement.
|
||||
|
||||
prefetch_cols
|
||||
a list of Column objects for which a client-side default
|
||||
was fired off. Applies to inserts and updates.
|
||||
|
||||
postfetch_cols
|
||||
a list of Column objects for which a server-side default or
|
||||
inline SQL expression value was fired off. Applies to inserts
|
||||
and updates.
|
||||
"""
|
||||
|
||||
def create_cursor(self):
|
||||
"""Return a new cursor generated from this ExecutionContext's
|
||||
connection.
|
||||
|
||||
Some dialects may wish to change the behavior of
|
||||
connection.cursor(), such as postgresql which may return a PG
|
||||
"server side" cursor.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def pre_exec(self):
|
||||
"""Called before an execution of a compiled statement.
|
||||
|
||||
If a compiled statement was passed to this ExecutionContext,
|
||||
the `statement` and `parameters` datamembers must be
|
||||
initialized after this statement is complete.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_exec(self):
|
||||
"""Called after the execution of a compiled statement.
|
||||
|
||||
If a compiled statement was passed to this ExecutionContext,
|
||||
the `last_insert_ids`, `last_inserted_params`, etc.
|
||||
datamembers should be available after this method completes.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def result(self):
|
||||
"""Return a result object corresponding to this ExecutionContext.
|
||||
|
||||
Returns a ResultProxy.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def handle_dbapi_exception(self, e):
|
||||
"""Receive a DBAPI exception which occurred upon execute, result
|
||||
fetch, etc."""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def should_autocommit_text(self, statement):
|
||||
"""Parse the given textual statement and return True if it refers to
|
||||
a "committable" statement"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def lastrow_has_defaults(self):
|
||||
"""Return True if the last INSERT or UPDATE row contained
|
||||
inlined or database-side defaults.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_rowcount(self):
|
||||
"""Return the DBAPI ``cursor.rowcount`` value, or in some
|
||||
cases an interpreted value.
|
||||
|
||||
See :attr:`.ResultProxy.rowcount` for details on this.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Connectable(object):
|
||||
"""Interface for an object which supports execution of SQL constructs.
|
||||
|
||||
The two implementations of :class:`.Connectable` are
|
||||
:class:`.Connection` and :class:`.Engine`.
|
||||
|
||||
Connectable must also implement the 'dialect' member which references a
|
||||
:class:`.Dialect` instance.
|
||||
|
||||
"""
|
||||
|
||||
def connect(self, **kwargs):
|
||||
"""Return a :class:`.Connection` object.
|
||||
|
||||
Depending on context, this may be ``self`` if this object
|
||||
is already an instance of :class:`.Connection`, or a newly
|
||||
procured :class:`.Connection` if this object is an instance
|
||||
of :class:`.Engine`.
|
||||
|
||||
"""
|
||||
|
||||
def contextual_connect(self):
|
||||
"""Return a :class:`.Connection` object which may be part of an ongoing
|
||||
context.
|
||||
|
||||
Depending on context, this may be ``self`` if this object
|
||||
is already an instance of :class:`.Connection`, or a newly
|
||||
procured :class:`.Connection` if this object is an instance
|
||||
of :class:`.Engine`.
|
||||
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@util.deprecated("0.7",
|
||||
"Use the create() method on the given schema "
|
||||
"object directly, i.e. :meth:`.Table.create`, "
|
||||
":meth:`.Index.create`, :meth:`.MetaData.create_all`")
|
||||
def create(self, entity, **kwargs):
|
||||
"""Emit CREATE statements for the given schema entity.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@util.deprecated("0.7",
|
||||
"Use the drop() method on the given schema "
|
||||
"object directly, i.e. :meth:`.Table.drop`, "
|
||||
":meth:`.Index.drop`, :meth:`.MetaData.drop_all`")
|
||||
def drop(self, entity, **kwargs):
|
||||
"""Emit DROP statements for the given schema entity.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
def execute(self, object, *multiparams, **params):
|
||||
"""Executes the given construct and returns a :class:`.ResultProxy`."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def scalar(self, object, *multiparams, **params):
|
||||
"""Executes and returns the first column of the first row.
|
||||
|
||||
The underlying cursor is closed after execution.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _run_visitor(self, visitorcallable, element,
|
||||
**kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _execute_clauseelement(self, elem, multiparams=None, params=None):
|
||||
raise NotImplementedError()
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/reflection.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -24,12 +24,14 @@ methods such as get_table_names, get_columns, etc.
|
||||
'name' attribute..
|
||||
"""
|
||||
|
||||
import sqlalchemy
|
||||
from sqlalchemy import exc, sql
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.util import topological
|
||||
from sqlalchemy.types import TypeEngine
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from .. import exc, sql
|
||||
from ..sql import schema as sa_schema
|
||||
from .. import util
|
||||
from ..sql.type_api import TypeEngine
|
||||
from ..util import deprecated
|
||||
from ..util import topological
|
||||
from .. import inspection
|
||||
from .base import Connectable
|
||||
|
||||
|
||||
@util.decorator
|
||||
@@ -39,8 +41,12 @@ def cache(fn, self, con, *args, **kw):
|
||||
return fn(self, con, *args, **kw)
|
||||
key = (
|
||||
fn.__name__,
|
||||
tuple(a for a in args if isinstance(a, basestring)),
|
||||
tuple((k, v) for k, v in kw.iteritems() if isinstance(v, (basestring, int, float)))
|
||||
tuple(a for a in args if isinstance(a, util.string_types)),
|
||||
tuple((k, v) for k, v in kw.items() if
|
||||
isinstance(v,
|
||||
util.string_types + util.int_types + (float, )
|
||||
)
|
||||
)
|
||||
)
|
||||
ret = info_cache.get(key)
|
||||
if ret is None:
|
||||
@@ -53,17 +59,24 @@ class Inspector(object):
|
||||
"""Performs database schema inspection.
|
||||
|
||||
The Inspector acts as a proxy to the reflection methods of the
|
||||
:class:`~sqlalchemy.engine.base.Dialect`, providing a
|
||||
:class:`~sqlalchemy.engine.interfaces.Dialect`, providing a
|
||||
consistent interface as well as caching support for previously
|
||||
fetched metadata.
|
||||
|
||||
The preferred method to construct an :class:`.Inspector` is via the
|
||||
:meth:`Inspector.from_engine` method. I.e.::
|
||||
A :class:`.Inspector` object is usually created via the
|
||||
:func:`.inspect` function::
|
||||
|
||||
from sqlalchemy import inspect, create_engine
|
||||
engine = create_engine('...')
|
||||
insp = inspect(engine)
|
||||
|
||||
The inspection method above is equivalent to using the
|
||||
:meth:`.Inspector.from_engine` method, i.e.::
|
||||
|
||||
engine = create_engine('...')
|
||||
insp = Inspector.from_engine(engine)
|
||||
|
||||
Where above, the :class:`~sqlalchemy.engine.base.Dialect` may opt
|
||||
Where above, the :class:`~sqlalchemy.engine.interfaces.Dialect` may opt
|
||||
to return an :class:`.Inspector` subclass that provides additional
|
||||
methods specific to the dialect's target database.
|
||||
|
||||
@@ -72,13 +85,13 @@ class Inspector(object):
|
||||
def __init__(self, bind):
|
||||
"""Initialize a new :class:`.Inspector`.
|
||||
|
||||
:param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
|
||||
:param bind: a :class:`~sqlalchemy.engine.Connectable`,
|
||||
which is typically an instance of
|
||||
:class:`~sqlalchemy.engine.base.Engine` or
|
||||
:class:`~sqlalchemy.engine.base.Connection`.
|
||||
:class:`~sqlalchemy.engine.Engine` or
|
||||
:class:`~sqlalchemy.engine.Connection`.
|
||||
|
||||
For a dialect-specific instance of :class:`.Inspector`, see
|
||||
:meth:`Inspector.from_engine`
|
||||
:meth:`.Inspector.from_engine`
|
||||
|
||||
"""
|
||||
# this might not be a connection, it could be an engine.
|
||||
@@ -99,17 +112,19 @@ class Inspector(object):
|
||||
|
||||
@classmethod
|
||||
def from_engine(cls, bind):
|
||||
"""Construct a new dialect-specific Inspector object from the given engine or connection.
|
||||
"""Construct a new dialect-specific Inspector object from the given
|
||||
engine or connection.
|
||||
|
||||
:param bind: a :class:`~sqlalchemy.engine.base.Connectable`,
|
||||
:param bind: a :class:`~sqlalchemy.engine.Connectable`,
|
||||
which is typically an instance of
|
||||
:class:`~sqlalchemy.engine.base.Engine` or
|
||||
:class:`~sqlalchemy.engine.base.Connection`.
|
||||
:class:`~sqlalchemy.engine.Engine` or
|
||||
:class:`~sqlalchemy.engine.Connection`.
|
||||
|
||||
This method differs from direct a direct constructor call of :class:`.Inspector`
|
||||
in that the :class:`~sqlalchemy.engine.base.Dialect` is given a chance to provide
|
||||
a dialect-specific :class:`.Inspector` instance, which may provide additional
|
||||
methods.
|
||||
This method differs from direct a direct constructor call of
|
||||
:class:`.Inspector` in that the
|
||||
:class:`~sqlalchemy.engine.interfaces.Dialect` is given a chance to
|
||||
provide a dialect-specific :class:`.Inspector` instance, which may
|
||||
provide additional methods.
|
||||
|
||||
See the example at :class:`.Inspector`.
|
||||
|
||||
@@ -118,6 +133,10 @@ class Inspector(object):
|
||||
return bind.dialect.inspector(bind)
|
||||
return Inspector(bind)
|
||||
|
||||
@inspection._inspects(Connectable)
|
||||
def _insp(bind):
|
||||
return Inspector.from_engine(bind)
|
||||
|
||||
@property
|
||||
def default_schema_name(self):
|
||||
"""Return the default schema name presented by the dialect
|
||||
@@ -139,14 +158,32 @@ class Inspector(object):
|
||||
return []
|
||||
|
||||
def get_table_names(self, schema=None, order_by=None):
|
||||
"""Return all table names in `schema`.
|
||||
"""Return all table names in referred to within a particular schema.
|
||||
|
||||
The names are expected to be real tables only, not views.
|
||||
Views are instead returned using the :meth:`.Inspector.get_view_names`
|
||||
method.
|
||||
|
||||
|
||||
:param schema: Schema name. If ``schema`` is left at ``None``, the
|
||||
database's default schema is
|
||||
used, else the named schema is searched. If the database does not
|
||||
support named schemas, behavior is undefined if ``schema`` is not
|
||||
passed as ``None``. For special quoting, use :class:`.quoted_name`.
|
||||
|
||||
:param schema: Optional, retrieve names from a non-default schema.
|
||||
:param order_by: Optional, may be the string "foreign_key" to sort
|
||||
the result on foreign key dependencies.
|
||||
the result on foreign key dependencies.
|
||||
|
||||
.. versionchanged:: 0.8 the "foreign_key" sorting sorts tables
|
||||
in order of dependee to dependent; that is, in creation
|
||||
order, rather than in drop order. This is to maintain
|
||||
consistency with similar features such as
|
||||
:attr:`.MetaData.sorted_tables` and :func:`.util.sort_tables`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.MetaData.sorted_tables`
|
||||
|
||||
This should probably not return view names or maybe it should return
|
||||
them with an indicator t or v.
|
||||
"""
|
||||
|
||||
if hasattr(self.dialect, 'get_table_names'):
|
||||
@@ -155,33 +192,40 @@ class Inspector(object):
|
||||
else:
|
||||
tnames = self.engine.table_names(schema)
|
||||
if order_by == 'foreign_key':
|
||||
import random
|
||||
random.shuffle(tnames)
|
||||
|
||||
tuples = []
|
||||
for tname in tnames:
|
||||
for fkey in self.get_foreign_keys(tname, schema):
|
||||
if tname != fkey['referred_table']:
|
||||
tuples.append((tname, fkey['referred_table']))
|
||||
tuples.append((fkey['referred_table'], tname))
|
||||
tnames = list(topological.sort(tuples, tnames))
|
||||
return tnames
|
||||
|
||||
def get_table_options(self, table_name, schema=None, **kw):
|
||||
"""Return a dictionary of options specified when the table of the given name was created.
|
||||
"""Return a dictionary of options specified when the table of the
|
||||
given name was created.
|
||||
|
||||
This currently includes some options that apply to MySQL tables.
|
||||
|
||||
:param table_name: string name of the table. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
:param schema: string schema name; if omitted, uses the default schema
|
||||
of the database connection. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
if hasattr(self.dialect, 'get_table_options'):
|
||||
return self.dialect.get_table_options(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
return self.dialect.get_table_options(
|
||||
self.bind, table_name, schema,
|
||||
info_cache=self.info_cache, **kw)
|
||||
return {}
|
||||
|
||||
def get_view_names(self, schema=None):
|
||||
"""Return all view names in `schema`.
|
||||
|
||||
:param schema: Optional, retrieve names from a non-default schema.
|
||||
For special quoting, use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_view_names(self.bind, schema,
|
||||
@@ -191,6 +235,8 @@ class Inspector(object):
|
||||
"""Return definition for `view_name`.
|
||||
|
||||
:param schema: Optional, retrieve names from a non-default schema.
|
||||
For special quoting, use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_view_definition(
|
||||
@@ -216,6 +262,14 @@ class Inspector(object):
|
||||
|
||||
attrs
|
||||
dict containing optional column attributes
|
||||
|
||||
:param table_name: string name of the table. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
:param schema: string schema name; if omitted, uses the default schema
|
||||
of the database connection. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
|
||||
col_defs = self.dialect.get_columns(self.bind, table_name, schema,
|
||||
@@ -228,6 +282,8 @@ class Inspector(object):
|
||||
col_def['type'] = coltype()
|
||||
return col_defs
|
||||
|
||||
@deprecated('0.7', 'Call to deprecated method get_primary_keys.'
|
||||
' Use get_pk_constraint instead.')
|
||||
def get_primary_keys(self, table_name, schema=None, **kw):
|
||||
"""Return information about primary keys in `table_name`.
|
||||
|
||||
@@ -235,11 +291,9 @@ class Inspector(object):
|
||||
primary key information as a list of column names.
|
||||
"""
|
||||
|
||||
pkeys = self.dialect.get_primary_keys(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
|
||||
return pkeys
|
||||
return self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)['constrained_columns']
|
||||
|
||||
def get_pk_constraint(self, table_name, schema=None, **kw):
|
||||
"""Return information about primary key constraint on `table_name`.
|
||||
@@ -253,14 +307,18 @@ class Inspector(object):
|
||||
name
|
||||
optional name of the primary key constraint.
|
||||
|
||||
:param table_name: string name of the table. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
:param schema: string schema name; if omitted, uses the default schema
|
||||
of the database connection. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
pkeys = self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
return self.dialect.get_pk_constraint(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
|
||||
return pkeys
|
||||
|
||||
|
||||
def get_foreign_keys(self, table_name, schema=None, **kw):
|
||||
"""Return information about foreign_keys in `table_name`.
|
||||
|
||||
@@ -283,15 +341,18 @@ class Inspector(object):
|
||||
name
|
||||
optional name of the foreign key constraint.
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_foreign_keys() method.
|
||||
:param table_name: string name of the table. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
:param schema: string schema name; if omitted, uses the default schema
|
||||
of the database connection. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
|
||||
fk_defs = self.dialect.get_foreign_keys(self.bind, table_name, schema,
|
||||
return self.dialect.get_foreign_keys(self.bind, table_name, schema,
|
||||
info_cache=self.info_cache,
|
||||
**kw)
|
||||
return fk_defs
|
||||
|
||||
def get_indexes(self, table_name, schema=None, **kw):
|
||||
"""Return information about indexes in `table_name`.
|
||||
@@ -308,17 +369,48 @@ class Inspector(object):
|
||||
unique
|
||||
boolean
|
||||
|
||||
\**kw
|
||||
other options passed to the dialect's get_indexes() method.
|
||||
:param table_name: string name of the table. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
:param schema: string schema name; if omitted, uses the default schema
|
||||
of the database connection. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
"""
|
||||
|
||||
indexes = self.dialect.get_indexes(self.bind, table_name,
|
||||
return self.dialect.get_indexes(self.bind, table_name,
|
||||
schema,
|
||||
info_cache=self.info_cache, **kw)
|
||||
return indexes
|
||||
|
||||
def get_unique_constraints(self, table_name, schema=None, **kw):
|
||||
"""Return information about unique constraints in `table_name`.
|
||||
|
||||
Given a string `table_name` and an optional string `schema`, return
|
||||
unique constraint information as a list of dicts with these keys:
|
||||
|
||||
name
|
||||
the unique constraint's name
|
||||
|
||||
column_names
|
||||
list of column names in order
|
||||
|
||||
:param table_name: string name of the table. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
:param schema: string schema name; if omitted, uses the default schema
|
||||
of the database connection. For special quoting,
|
||||
use :class:`.quoted_name`.
|
||||
|
||||
.. versionadded:: 0.8.4
|
||||
|
||||
"""
|
||||
|
||||
return self.dialect.get_unique_constraints(
|
||||
self.bind, table_name, schema, info_cache=self.info_cache, **kw)
|
||||
|
||||
def reflecttable(self, table, include_columns, exclude_columns=()):
|
||||
"""Given a Table object, load its internal constructs based on introspection.
|
||||
"""Given a Table object, load its internal constructs based on
|
||||
introspection.
|
||||
|
||||
This is the underlying method used by most dialects to produce
|
||||
table reflection. Direct usage is like::
|
||||
@@ -341,7 +433,8 @@ class Inspector(object):
|
||||
|
||||
# table attributes we might need.
|
||||
reflection_options = dict(
|
||||
(k, table.kwargs.get(k)) for k in dialect.reflection_options if k in table.kwargs)
|
||||
(k, table.kwargs.get(k))
|
||||
for k in dialect.reflection_options if k in table.kwargs)
|
||||
|
||||
schema = table.schema
|
||||
table_name = table.name
|
||||
@@ -354,22 +447,25 @@ class Inspector(object):
|
||||
# table.kwargs will need to be passed to each reflection method. Make
|
||||
# sure keywords are strings.
|
||||
tblkw = table.kwargs.copy()
|
||||
for (k, v) in tblkw.items():
|
||||
for (k, v) in list(tblkw.items()):
|
||||
del tblkw[k]
|
||||
tblkw[str(k)] = v
|
||||
|
||||
# Py2K
|
||||
if isinstance(schema, str):
|
||||
schema = schema.decode(dialect.encoding)
|
||||
if isinstance(table_name, str):
|
||||
table_name = table_name.decode(dialect.encoding)
|
||||
# end Py2K
|
||||
if util.py2k:
|
||||
if isinstance(schema, str):
|
||||
schema = schema.decode(dialect.encoding)
|
||||
if isinstance(table_name, str):
|
||||
table_name = table_name.decode(dialect.encoding)
|
||||
|
||||
# columns
|
||||
found_table = False
|
||||
cols_by_orig_name = {}
|
||||
|
||||
for col_d in self.get_columns(table_name, schema, **tblkw):
|
||||
found_table = True
|
||||
table.dispatch.column_reflect(table, col_d)
|
||||
orig_name = col_d['name']
|
||||
|
||||
table.dispatch.column_reflect(self, table, col_d)
|
||||
|
||||
name = col_d['name']
|
||||
if include_columns and name not in include_columns:
|
||||
@@ -387,8 +483,9 @@ class Inspector(object):
|
||||
|
||||
colargs = []
|
||||
if col_d.get('default') is not None:
|
||||
# the "default" value is assumed to be a literal SQL expression,
|
||||
# so is wrapped in text() so that no quoting occurs on re-issuance.
|
||||
# the "default" value is assumed to be a literal SQL
|
||||
# expression, so is wrapped in text() so that no quoting
|
||||
# occurs on re-issuance.
|
||||
colargs.append(
|
||||
sa_schema.DefaultClause(
|
||||
sql.text(col_d['default']), _reflected=True
|
||||
@@ -396,7 +493,7 @@ class Inspector(object):
|
||||
)
|
||||
|
||||
if 'sequence' in col_d:
|
||||
# TODO: mssql, maxdb and sybase are using this.
|
||||
# TODO: mssql and sybase are using this.
|
||||
seq = col_d['sequence']
|
||||
sequence = sa_schema.Sequence(seq['name'], 1, 1)
|
||||
if 'start' in seq:
|
||||
@@ -405,7 +502,9 @@ class Inspector(object):
|
||||
sequence.increment = seq['increment']
|
||||
colargs.append(sequence)
|
||||
|
||||
col = sa_schema.Column(name, coltype, *colargs, **col_kw)
|
||||
cols_by_orig_name[orig_name] = col = \
|
||||
sa_schema.Column(name, coltype, *colargs, **col_kw)
|
||||
|
||||
table.append_column(col)
|
||||
|
||||
if not found_table:
|
||||
@@ -414,11 +513,18 @@ class Inspector(object):
|
||||
# Primary keys
|
||||
pk_cons = self.get_pk_constraint(table_name, schema, **tblkw)
|
||||
if pk_cons:
|
||||
pk_cols = [table.c[pk]
|
||||
for pk in pk_cons['constrained_columns']
|
||||
if pk in table.c and pk not in exclude_columns
|
||||
] + [pk for pk in table.primary_key if pk.key in exclude_columns]
|
||||
primary_key_constraint = sa_schema.PrimaryKeyConstraint(name=pk_cons.get('name'),
|
||||
pk_cols = [
|
||||
cols_by_orig_name[pk]
|
||||
for pk in pk_cons['constrained_columns']
|
||||
if pk in cols_by_orig_name and pk not in exclude_columns
|
||||
]
|
||||
pk_cols += [
|
||||
pk
|
||||
for pk in table.primary_key
|
||||
if pk.key in exclude_columns
|
||||
]
|
||||
primary_key_constraint = sa_schema.PrimaryKeyConstraint(
|
||||
name=pk_cons.get('name'),
|
||||
*pk_cols
|
||||
)
|
||||
|
||||
@@ -428,7 +534,16 @@ class Inspector(object):
|
||||
fkeys = self.get_foreign_keys(table_name, schema, **tblkw)
|
||||
for fkey_d in fkeys:
|
||||
conname = fkey_d['name']
|
||||
constrained_columns = fkey_d['constrained_columns']
|
||||
# look for columns by orig name in cols_by_orig_name,
|
||||
# but support columns that are in-Python only as fallback
|
||||
constrained_columns = [
|
||||
cols_by_orig_name[c].key
|
||||
if c in cols_by_orig_name else c
|
||||
for c in fkey_d['constrained_columns']
|
||||
]
|
||||
if exclude_columns and set(constrained_columns).intersection(
|
||||
exclude_columns):
|
||||
continue
|
||||
referred_schema = fkey_d['referred_schema']
|
||||
referred_table = fkey_d['referred_table']
|
||||
referred_columns = fkey_d['referred_columns']
|
||||
@@ -449,9 +564,14 @@ class Inspector(object):
|
||||
)
|
||||
for column in referred_columns:
|
||||
refspec.append(".".join([referred_table, column]))
|
||||
if 'options' in fkey_d:
|
||||
options = fkey_d['options']
|
||||
else:
|
||||
options = {}
|
||||
table.append_constraint(
|
||||
sa_schema.ForeignKeyConstraint(constrained_columns, refspec,
|
||||
conname, link_to_name=True))
|
||||
conname, link_to_name=True,
|
||||
**options))
|
||||
# Indexes
|
||||
indexes = self.get_indexes(table_name, schema)
|
||||
for index_d in indexes:
|
||||
@@ -465,5 +585,11 @@ class Inspector(object):
|
||||
"Omitting %s KEY for (%s), key covers omitted columns." %
|
||||
(flavor, ', '.join(columns)))
|
||||
continue
|
||||
sa_schema.Index(name, *[table.columns[c] for c in columns],
|
||||
# look for columns by orig name in cols_by_orig_name,
|
||||
# but support columns that are in-Python only as fallback
|
||||
sa_schema.Index(name, *[
|
||||
cols_by_orig_name[c] if c in cols_by_orig_name
|
||||
else table.c[c]
|
||||
for c in columns
|
||||
],
|
||||
**dict(unique=unique))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
# engine/strategies.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -49,18 +49,27 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
|
||||
dialect_cls = u.get_dialect()
|
||||
|
||||
if kwargs.pop('_coerce_config', False):
|
||||
def pop_kwarg(key, default=None):
|
||||
value = kwargs.pop(key, default)
|
||||
if key in dialect_cls.engine_config_types:
|
||||
value = dialect_cls.engine_config_types[key](value)
|
||||
return value
|
||||
else:
|
||||
pop_kwarg = kwargs.pop
|
||||
|
||||
dialect_args = {}
|
||||
# consume dialect arguments from kwargs
|
||||
for k in util.get_cls_kwargs(dialect_cls):
|
||||
if k in kwargs:
|
||||
dialect_args[k] = kwargs.pop(k)
|
||||
dialect_args[k] = pop_kwarg(k)
|
||||
|
||||
dbapi = kwargs.pop('module', None)
|
||||
if dbapi is None:
|
||||
dbapi_args = {}
|
||||
for k in util.get_func_kwargs(dialect_cls.dbapi):
|
||||
if k in kwargs:
|
||||
dbapi_args[k] = kwargs.pop(k)
|
||||
dbapi_args[k] = pop_kwarg(k)
|
||||
dbapi = dialect_cls.dbapi(**dbapi_args)
|
||||
|
||||
dialect_args['dbapi'] = dbapi
|
||||
@@ -70,33 +79,26 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
|
||||
# assemble connection arguments
|
||||
(cargs, cparams) = dialect.create_connect_args(u)
|
||||
cparams.update(kwargs.pop('connect_args', {}))
|
||||
cparams.update(pop_kwarg('connect_args', {}))
|
||||
|
||||
# look for existing pool or create
|
||||
pool = kwargs.pop('pool', None)
|
||||
pool = pop_kwarg('pool', None)
|
||||
if pool is None:
|
||||
def connect():
|
||||
try:
|
||||
return dialect.connect(*cargs, **cparams)
|
||||
except Exception, e:
|
||||
# Py3K
|
||||
#raise exc.DBAPIError.instance(None, None,
|
||||
# e, dialect.dbapi.Error,
|
||||
# connection_invalidated=
|
||||
# dialect.is_disconnect(e, None, None)
|
||||
# ) from e
|
||||
# Py2K
|
||||
import sys
|
||||
raise exc.DBAPIError.instance(
|
||||
None, None, e, dialect.dbapi.Error,
|
||||
connection_invalidated=
|
||||
dialect.is_disconnect(e, None, None)), \
|
||||
None, sys.exc_info()[2]
|
||||
# end Py2K
|
||||
except dialect.dbapi.Error as e:
|
||||
invalidated = dialect.is_disconnect(e, None, None)
|
||||
util.raise_from_cause(
|
||||
exc.DBAPIError.instance(None, None,
|
||||
e, dialect.dbapi.Error,
|
||||
connection_invalidated=invalidated
|
||||
)
|
||||
)
|
||||
|
||||
creator = kwargs.pop('creator', connect)
|
||||
creator = pop_kwarg('creator', connect)
|
||||
|
||||
poolclass = kwargs.pop('poolclass', None)
|
||||
poolclass = pop_kwarg('poolclass', None)
|
||||
if poolclass is None:
|
||||
poolclass = dialect_cls.get_pool_class(u)
|
||||
pool_args = {}
|
||||
@@ -107,13 +109,13 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
'echo': 'echo_pool',
|
||||
'timeout': 'pool_timeout',
|
||||
'recycle': 'pool_recycle',
|
||||
'events':'pool_events',
|
||||
'use_threadlocal':'pool_threadlocal',
|
||||
'reset_on_return':'pool_reset_on_return'}
|
||||
'events': 'pool_events',
|
||||
'use_threadlocal': 'pool_threadlocal',
|
||||
'reset_on_return': 'pool_reset_on_return'}
|
||||
for k in util.get_cls_kwargs(poolclass):
|
||||
tk = translate.get(k, k)
|
||||
if tk in kwargs:
|
||||
pool_args[k] = kwargs.pop(tk)
|
||||
pool_args[k] = pop_kwarg(tk)
|
||||
pool = poolclass(creator, **pool_args)
|
||||
else:
|
||||
if isinstance(pool, poollib._DBProxy):
|
||||
@@ -126,7 +128,7 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
engine_args = {}
|
||||
for k in util.get_cls_kwargs(engineclass):
|
||||
if k in kwargs:
|
||||
engine_args[k] = kwargs.pop(k)
|
||||
engine_args[k] = pop_kwarg(k)
|
||||
|
||||
_initialize = kwargs.pop('_initialize', True)
|
||||
|
||||
@@ -147,7 +149,8 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
do_on_connect = dialect.on_connect()
|
||||
if do_on_connect:
|
||||
def on_connect(dbapi_connection, connection_record):
|
||||
conn = getattr(dbapi_connection, '_sqla_unwrap', dbapi_connection)
|
||||
conn = getattr(
|
||||
dbapi_connection, '_sqla_unwrap', dbapi_connection)
|
||||
if conn is None:
|
||||
return
|
||||
do_on_connect(conn)
|
||||
@@ -155,14 +158,10 @@ class DefaultEngineStrategy(EngineStrategy):
|
||||
event.listen(pool, 'first_connect', on_connect)
|
||||
event.listen(pool, 'connect', on_connect)
|
||||
|
||||
@util.only_once
|
||||
def first_connect(dbapi_connection, connection_record):
|
||||
c = base.Connection(engine, connection=dbapi_connection)
|
||||
|
||||
# TODO: removing this allows the on connect activities
|
||||
# to generate events. tests currently assume these aren't
|
||||
# sent. do we want users to get all the initial connect
|
||||
# activities as events ?
|
||||
c._has_events = False
|
||||
c = base.Connection(engine, connection=dbapi_connection,
|
||||
_has_events=False)
|
||||
|
||||
dialect.initialize(c)
|
||||
event.listen(pool, 'first_connect', first_connect)
|
||||
@@ -238,12 +237,14 @@ class MockEngineStrategy(EngineStrategy):
|
||||
kwargs['checkfirst'] = False
|
||||
from sqlalchemy.engine import ddl
|
||||
|
||||
ddl.SchemaGenerator(self.dialect, self, **kwargs).traverse_single(entity)
|
||||
ddl.SchemaGenerator(
|
||||
self.dialect, self, **kwargs).traverse_single(entity)
|
||||
|
||||
def drop(self, entity, **kwargs):
|
||||
kwargs['checkfirst'] = False
|
||||
from sqlalchemy.engine import ddl
|
||||
ddl.SchemaDropper(self.dialect, self, **kwargs).traverse_single(entity)
|
||||
ddl.SchemaDropper(
|
||||
self.dialect, self, **kwargs).traverse_single(entity)
|
||||
|
||||
def _run_visitor(self, visitorcallable, element,
|
||||
connection=None,
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
# engine/threadlocal.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides a thread-local transactional wrapper around the root Engine class.
|
||||
|
||||
The ``threadlocal`` module is invoked when using the ``strategy="threadlocal"`` flag
|
||||
with :func:`~sqlalchemy.engine.create_engine`. This module is semi-private and is
|
||||
invoked automatically when the threadlocal engine strategy is used.
|
||||
The ``threadlocal`` module is invoked when using the
|
||||
``strategy="threadlocal"`` flag with :func:`~sqlalchemy.engine.create_engine`.
|
||||
This module is semi-private and is invoked automatically when the threadlocal
|
||||
engine strategy is used.
|
||||
"""
|
||||
|
||||
from sqlalchemy import util, event
|
||||
from sqlalchemy.engine import base
|
||||
from .. import util
|
||||
from . import base
|
||||
import weakref
|
||||
|
||||
|
||||
class TLConnection(base.Connection):
|
||||
|
||||
def __init__(self, *arg, **kw):
|
||||
super(TLConnection, self).__init__(*arg, **kw)
|
||||
self.__opencount = 0
|
||||
@@ -33,16 +36,18 @@ class TLConnection(base.Connection):
|
||||
self.__opencount = 0
|
||||
base.Connection.close(self)
|
||||
|
||||
class TLEngine(base.Engine):
|
||||
"""An Engine that includes support for thread-local managed transactions."""
|
||||
|
||||
class TLEngine(base.Engine):
|
||||
"""An Engine that includes support for thread-local managed
|
||||
transactions.
|
||||
|
||||
"""
|
||||
_tl_connection_cls = TLConnection
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TLEngine, self).__init__(*args, **kwargs)
|
||||
self._connections = util.threading.local()
|
||||
|
||||
|
||||
def contextual_connect(self, **kw):
|
||||
if not hasattr(self._connections, 'conn'):
|
||||
connection = None
|
||||
@@ -52,21 +57,24 @@ class TLEngine(base.Engine):
|
||||
if connection is None or connection.closed:
|
||||
# guards against pool-level reapers, if desired.
|
||||
# or not connection.connection.is_valid:
|
||||
connection = self._tl_connection_cls(self, self.pool.connect(), **kw)
|
||||
self._connections.conn = conn = weakref.ref(connection)
|
||||
connection = self._tl_connection_cls(
|
||||
self, self.pool.connect(), **kw)
|
||||
self._connections.conn = weakref.ref(connection)
|
||||
|
||||
return connection._increment_connect()
|
||||
|
||||
def begin_twophase(self, xid=None):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(self.contextual_connect().begin_twophase(xid=xid))
|
||||
self._connections.trans.append(
|
||||
self.contextual_connect().begin_twophase(xid=xid))
|
||||
return self
|
||||
|
||||
def begin_nested(self):
|
||||
if not hasattr(self._connections, 'trans'):
|
||||
self._connections.trans = []
|
||||
self._connections.trans.append(self.contextual_connect().begin_nested())
|
||||
self._connections.trans.append(
|
||||
self.contextual_connect().begin_nested())
|
||||
return self
|
||||
|
||||
def begin(self):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# engine/url.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
@@ -7,13 +7,16 @@
|
||||
"""Provides the :class:`~sqlalchemy.engine.url.URL` class which encapsulates
|
||||
information about a database connection specification.
|
||||
|
||||
The URL object is created automatically when :func:`~sqlalchemy.engine.create_engine` is called
|
||||
with a string argument; alternatively, the URL is a public-facing construct which can
|
||||
The URL object is created automatically when
|
||||
:func:`~sqlalchemy.engine.create_engine` is called with a string
|
||||
argument; alternatively, the URL is a public-facing construct which can
|
||||
be used directly and is also accepted directly by ``create_engine()``.
|
||||
"""
|
||||
|
||||
import re, urllib
|
||||
from sqlalchemy import exc, util
|
||||
import re
|
||||
from .. import exc, util
|
||||
from . import Dialect
|
||||
from ..dialects import registry
|
||||
|
||||
|
||||
class URL(object):
|
||||
@@ -21,8 +24,8 @@ class URL(object):
|
||||
Represent the components of a URL used to connect to a database.
|
||||
|
||||
This object is suitable to be passed directly to a
|
||||
``create_engine()`` call. The fields of the URL are parsed from a
|
||||
string by the ``module-level make_url()`` function. the string
|
||||
:func:`~sqlalchemy.create_engine` call. The fields of the URL are parsed from a
|
||||
string by the :func:`.make_url` function. the string
|
||||
format of the URL is an RFC-1738-style string.
|
||||
|
||||
All initialization parameters are available as public attributes.
|
||||
@@ -59,25 +62,35 @@ class URL(object):
|
||||
self.database = database
|
||||
self.query = query or {}
|
||||
|
||||
def __str__(self):
|
||||
def __to_string__(self, hide_password=True):
|
||||
s = self.drivername + "://"
|
||||
if self.username is not None:
|
||||
s += self.username
|
||||
s += _rfc_1738_quote(self.username)
|
||||
if self.password is not None:
|
||||
s += ':' + urllib.quote_plus(self.password)
|
||||
s += ':' + ('***' if hide_password
|
||||
else _rfc_1738_quote(self.password))
|
||||
s += "@"
|
||||
if self.host is not None:
|
||||
s += self.host
|
||||
if ':' in self.host:
|
||||
s += "[%s]" % self.host
|
||||
else:
|
||||
s += self.host
|
||||
if self.port is not None:
|
||||
s += ':' + str(self.port)
|
||||
if self.database is not None:
|
||||
s += '/' + self.database
|
||||
if self.query:
|
||||
keys = self.query.keys()
|
||||
keys = list(self.query)
|
||||
keys.sort()
|
||||
s += '?' + "&".join("%s=%s" % (k, self.query[k]) for k in keys)
|
||||
return s
|
||||
|
||||
def __str__(self):
|
||||
return self.__to_string__(hide_password=False)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__to_string__()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self))
|
||||
|
||||
@@ -96,49 +109,20 @@ class URL(object):
|
||||
to this URL's driver name.
|
||||
"""
|
||||
|
||||
try:
|
||||
if '+' in self.drivername:
|
||||
dialect, driver = self.drivername.split('+')
|
||||
else:
|
||||
dialect, driver = self.drivername, 'base'
|
||||
|
||||
module = __import__('sqlalchemy.dialects.%s' % (dialect, )).dialects
|
||||
module = getattr(module, dialect)
|
||||
if hasattr(module, driver):
|
||||
module = getattr(module, driver)
|
||||
else:
|
||||
module = self._load_entry_point()
|
||||
if module is None:
|
||||
raise exc.ArgumentError(
|
||||
"Could not determine dialect for '%s'." %
|
||||
self.drivername)
|
||||
|
||||
return module.dialect
|
||||
except ImportError:
|
||||
module = self._load_entry_point()
|
||||
if module is not None:
|
||||
return module
|
||||
else:
|
||||
raise exc.ArgumentError(
|
||||
"Could not determine dialect for '%s'." % self.drivername)
|
||||
|
||||
def _load_entry_point(self):
|
||||
"""attempt to load this url's dialect from entry points, or return None
|
||||
if pkg_resources is not installed or there is no matching entry point.
|
||||
|
||||
Raise ImportError if the actual load fails.
|
||||
|
||||
"""
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
for res in pkg_resources.iter_entry_points('sqlalchemy.dialects'):
|
||||
if res.name == self.drivername.replace("+", "."):
|
||||
return res.load()
|
||||
if '+' not in self.drivername:
|
||||
name = self.drivername
|
||||
else:
|
||||
return None
|
||||
name = self.drivername.replace('+', '.')
|
||||
cls = registry.load(name)
|
||||
# check for legacy dialects that
|
||||
# would return a module with 'dialect' as the
|
||||
# actual class
|
||||
if hasattr(cls, 'dialect') and \
|
||||
isinstance(cls.dialect, type) and \
|
||||
issubclass(cls.dialect, Dialect):
|
||||
return cls.dialect
|
||||
else:
|
||||
return cls
|
||||
|
||||
def translate_connect_args(self, names=[], **kw):
|
||||
"""Translate url attributes into a dictionary of connection arguments.
|
||||
@@ -150,8 +134,8 @@ class URL(object):
|
||||
|
||||
:param \**kw: Optional, alternate key names for url attributes.
|
||||
|
||||
:param names: Deprecated. Same purpose as the keyword-based alternate names,
|
||||
but correlates the name to the original positionally.
|
||||
:param names: Deprecated. Same purpose as the keyword-based alternate
|
||||
names, but correlates the name to the original positionally.
|
||||
"""
|
||||
|
||||
translated = {}
|
||||
@@ -167,6 +151,7 @@ class URL(object):
|
||||
translated[name] = getattr(self, sname)
|
||||
return translated
|
||||
|
||||
|
||||
def make_url(name_or_url):
|
||||
"""Given a string or unicode instance, produce a new URL instance.
|
||||
|
||||
@@ -174,25 +159,28 @@ def make_url(name_or_url):
|
||||
existing URL object is passed, just returns the object.
|
||||
"""
|
||||
|
||||
if isinstance(name_or_url, basestring):
|
||||
if isinstance(name_or_url, util.string_types):
|
||||
return _parse_rfc1738_args(name_or_url)
|
||||
else:
|
||||
return name_or_url
|
||||
|
||||
|
||||
def _parse_rfc1738_args(name):
|
||||
pattern = re.compile(r'''
|
||||
(?P<name>[\w\+]+)://
|
||||
(?:
|
||||
(?P<username>[^:/]*)
|
||||
(?::(?P<password>[^/]*))?
|
||||
(?::(?P<password>.*))?
|
||||
@)?
|
||||
(?:
|
||||
(?P<host>[^/:]*)
|
||||
(?:
|
||||
\[(?P<ipv6host>[^/]+)\] |
|
||||
(?P<ipv4host>[^/:]+)
|
||||
)?
|
||||
(?::(?P<port>[^/]*))?
|
||||
)?
|
||||
(?:/(?P<database>.*))?
|
||||
'''
|
||||
, re.X)
|
||||
''', re.X)
|
||||
|
||||
m = pattern.match(name)
|
||||
if m is not None:
|
||||
@@ -201,28 +189,39 @@ def _parse_rfc1738_args(name):
|
||||
tokens = components['database'].split('?', 2)
|
||||
components['database'] = tokens[0]
|
||||
query = (len(tokens) > 1 and dict(util.parse_qsl(tokens[1]))) or None
|
||||
# Py2K
|
||||
if query is not None:
|
||||
if util.py2k and query is not None:
|
||||
query = dict((k.encode('ascii'), query[k]) for k in query)
|
||||
# end Py2K
|
||||
else:
|
||||
query = None
|
||||
components['query'] = query
|
||||
|
||||
if components['password'] is not None:
|
||||
components['password'] = urllib.unquote_plus(components['password'])
|
||||
if components['username'] is not None:
|
||||
components['username'] = _rfc_1738_unquote(components['username'])
|
||||
|
||||
if components['password'] is not None:
|
||||
components['password'] = _rfc_1738_unquote(components['password'])
|
||||
|
||||
ipv4host = components.pop('ipv4host')
|
||||
ipv6host = components.pop('ipv6host')
|
||||
components['host'] = ipv4host or ipv6host
|
||||
name = components.pop('name')
|
||||
return URL(name, **components)
|
||||
else:
|
||||
raise exc.ArgumentError(
|
||||
"Could not parse rfc1738 URL from string '%s'" % name)
|
||||
|
||||
|
||||
def _rfc_1738_quote(text):
|
||||
return re.sub(r'[:@/]', lambda m: "%%%X" % ord(m.group(0)), text)
|
||||
|
||||
def _rfc_1738_unquote(text):
|
||||
return util.unquote(text)
|
||||
|
||||
def _parse_keyvalue_args(name):
|
||||
m = re.match( r'(\w+)://(.*)', name)
|
||||
m = re.match(r'(\w+)://(.*)', name)
|
||||
if m is not None:
|
||||
(name, args) = m.group(1, 2)
|
||||
opts = dict( util.parse_qsl( args ) )
|
||||
opts = dict(util.parse_qsl(args))
|
||||
return URL(name, *opts)
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# engine/util.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .. import util
|
||||
|
||||
def connection_memoize(key):
|
||||
"""Decorator, memoize a function in a connection.info stash.
|
||||
|
||||
Only applicable to functions which take no arguments other than a
|
||||
connection. The memo will be stored in ``connection.info[key]``.
|
||||
"""
|
||||
|
||||
@util.decorator
|
||||
def decorated(fn, self, connection):
|
||||
connection = connection.connect()
|
||||
try:
|
||||
return connection.info[key]
|
||||
except KeyError:
|
||||
connection.info[key] = val = fn(self, connection)
|
||||
return val
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
def py_fallback():
|
||||
def _distill_params(multiparams, params):
|
||||
"""Given arguments from the calling form *multiparams, **params,
|
||||
return a list of bind parameter structures, usually a list of
|
||||
dictionaries.
|
||||
|
||||
In the case of 'raw' execution which accepts positional parameters,
|
||||
it may be a list of tuples or lists.
|
||||
|
||||
"""
|
||||
|
||||
if not multiparams:
|
||||
if params:
|
||||
return [params]
|
||||
else:
|
||||
return []
|
||||
elif len(multiparams) == 1:
|
||||
zero = multiparams[0]
|
||||
if isinstance(zero, (list, tuple)):
|
||||
if not zero or hasattr(zero[0], '__iter__') and \
|
||||
not hasattr(zero[0], 'strip'):
|
||||
# execute(stmt, [{}, {}, {}, ...])
|
||||
# execute(stmt, [(), (), (), ...])
|
||||
return zero
|
||||
else:
|
||||
# execute(stmt, ("value", "value"))
|
||||
return [zero]
|
||||
elif hasattr(zero, 'keys'):
|
||||
# execute(stmt, {"key":"value"})
|
||||
return [zero]
|
||||
else:
|
||||
# execute(stmt, "value")
|
||||
return [[zero]]
|
||||
else:
|
||||
if hasattr(multiparams[0], '__iter__') and \
|
||||
not hasattr(multiparams[0], 'strip'):
|
||||
return multiparams
|
||||
else:
|
||||
return [multiparams]
|
||||
|
||||
return locals()
|
||||
try:
|
||||
from sqlalchemy.cutils import _distill_params
|
||||
except ImportError:
|
||||
globals().update(py_fallback())
|
||||
@@ -1,460 +0,0 @@
|
||||
# sqlalchemy/event.py
|
||||
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Base event API."""
|
||||
|
||||
from sqlalchemy import util, exc
|
||||
import weakref
|
||||
|
||||
CANCEL = util.symbol('CANCEL')
|
||||
NO_RETVAL = util.symbol('NO_RETVAL')
|
||||
|
||||
def listen(target, identifier, fn, *args, **kw):
|
||||
"""Register a listener function for the given target.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
def unique_constraint_name(const, table):
|
||||
const.name = "uq_%s_%s" % (
|
||||
table.name,
|
||||
list(const.columns)[0].name
|
||||
)
|
||||
event.listen(
|
||||
UniqueConstraint,
|
||||
"after_parent_attach",
|
||||
unique_constraint_name)
|
||||
|
||||
"""
|
||||
|
||||
for evt_cls in _registrars[identifier]:
|
||||
tgt = evt_cls._accept_with(target)
|
||||
if tgt is not None:
|
||||
tgt.dispatch._listen(tgt, identifier, fn, *args, **kw)
|
||||
return
|
||||
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
|
||||
(identifier, target))
|
||||
|
||||
def listens_for(target, identifier, *args, **kw):
|
||||
"""Decorate a function as a listener for the given target + identifier.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
@event.listens_for(UniqueConstraint, "after_parent_attach")
|
||||
def unique_constraint_name(const, table):
|
||||
const.name = "uq_%s_%s" % (
|
||||
table.name,
|
||||
list(const.columns)[0].name
|
||||
)
|
||||
"""
|
||||
def decorate(fn):
|
||||
listen(target, identifier, fn, *args, **kw)
|
||||
return fn
|
||||
return decorate
|
||||
|
||||
def remove(target, identifier, fn):
|
||||
"""Remove an event listener.
|
||||
|
||||
Note that some event removals, particularly for those event dispatchers
|
||||
which create wrapper functions and secondary even listeners, may not yet
|
||||
be supported.
|
||||
|
||||
"""
|
||||
for evt_cls in _registrars[identifier]:
|
||||
for tgt in evt_cls._accept_with(target):
|
||||
tgt.dispatch._remove(identifier, tgt, fn)
|
||||
return
|
||||
|
||||
_registrars = util.defaultdict(list)
|
||||
|
||||
def _is_event_name(name):
|
||||
return not name.startswith('_') and name != 'dispatch'
|
||||
|
||||
class _UnpickleDispatch(object):
|
||||
"""Serializable callable that re-generates an instance of :class:`_Dispatch`
|
||||
given a particular :class:`.Events` subclass.
|
||||
|
||||
"""
|
||||
def __call__(self, _parent_cls):
|
||||
for cls in _parent_cls.__mro__:
|
||||
if 'dispatch' in cls.__dict__:
|
||||
return cls.__dict__['dispatch'].dispatch_cls(_parent_cls)
|
||||
else:
|
||||
raise AttributeError("No class with a 'dispatch' member present.")
|
||||
|
||||
class _Dispatch(object):
|
||||
"""Mirror the event listening definitions of an Events class with
|
||||
listener collections.
|
||||
|
||||
Classes which define a "dispatch" member will return a
|
||||
non-instantiated :class:`._Dispatch` subclass when the member
|
||||
is accessed at the class level. When the "dispatch" member is
|
||||
accessed at the instance level of its owner, an instance
|
||||
of the :class:`._Dispatch` class is returned.
|
||||
|
||||
A :class:`._Dispatch` class is generated for each :class:`.Events`
|
||||
class defined, by the :func:`._create_dispatcher_class` function.
|
||||
The original :class:`.Events` classes remain untouched.
|
||||
This decouples the construction of :class:`.Events` subclasses from
|
||||
the implementation used by the event internals, and allows
|
||||
inspecting tools like Sphinx to work in an unsurprising
|
||||
way against the public API.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, _parent_cls):
|
||||
self._parent_cls = _parent_cls
|
||||
|
||||
def __reduce__(self):
|
||||
return _UnpickleDispatch(), (self._parent_cls, )
|
||||
|
||||
def _update(self, other, only_propagate=True):
|
||||
"""Populate from the listeners in another :class:`_Dispatch`
|
||||
object."""
|
||||
|
||||
for ls in _event_descriptors(other):
|
||||
getattr(self, ls.name).\
|
||||
for_modify(self)._update(ls, only_propagate=only_propagate)
|
||||
|
||||
def _event_descriptors(target):
|
||||
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
|
||||
|
||||
class _EventMeta(type):
|
||||
"""Intercept new Event subclasses and create
|
||||
associated _Dispatch classes."""
|
||||
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
_create_dispatcher_class(cls, classname, bases, dict_)
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
def _create_dispatcher_class(cls, classname, bases, dict_):
|
||||
"""Create a :class:`._Dispatch` class corresponding to an
|
||||
:class:`.Events` class."""
|
||||
|
||||
# there's all kinds of ways to do this,
|
||||
# i.e. make a Dispatch class that shares the '_listen' method
|
||||
# of the Event class, this is the straight monkeypatch.
|
||||
dispatch_base = getattr(cls, 'dispatch', _Dispatch)
|
||||
cls.dispatch = dispatch_cls = type("%sDispatch" % classname,
|
||||
(dispatch_base, ), {})
|
||||
dispatch_cls._listen = cls._listen
|
||||
dispatch_cls._clear = cls._clear
|
||||
|
||||
for k in dict_:
|
||||
if _is_event_name(k):
|
||||
setattr(dispatch_cls, k, _DispatchDescriptor(dict_[k]))
|
||||
_registrars[k].append(cls)
|
||||
|
||||
def _remove_dispatcher(cls):
|
||||
for k in dir(cls):
|
||||
if _is_event_name(k):
|
||||
_registrars[k].remove(cls)
|
||||
if not _registrars[k]:
|
||||
del _registrars[k]
|
||||
|
||||
class Events(object):
|
||||
"""Define event listening functions for a particular target type."""
|
||||
|
||||
|
||||
__metaclass__ = _EventMeta
|
||||
|
||||
@classmethod
|
||||
def _accept_with(cls, target):
|
||||
# Mapper, ClassManager, Session override this to
|
||||
# also accept classes, scoped_sessions, sessionmakers, etc.
|
||||
if hasattr(target, 'dispatch') and (
|
||||
isinstance(target.dispatch, cls.dispatch) or \
|
||||
isinstance(target.dispatch, type) and \
|
||||
issubclass(target.dispatch, cls.dispatch)
|
||||
):
|
||||
return target
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, target, identifier, fn, propagate=False, insert=False):
|
||||
if insert:
|
||||
getattr(target.dispatch, identifier).\
|
||||
for_modify(target.dispatch).insert(fn, target, propagate)
|
||||
else:
|
||||
getattr(target.dispatch, identifier).\
|
||||
for_modify(target.dispatch).append(fn, target, propagate)
|
||||
|
||||
@classmethod
|
||||
def _remove(cls, target, identifier, fn):
|
||||
getattr(target.dispatch, identifier).remove(fn, target)
|
||||
|
||||
@classmethod
|
||||
def _clear(cls):
|
||||
for attr in dir(cls.dispatch):
|
||||
if _is_event_name(attr):
|
||||
getattr(cls.dispatch, attr).clear()
|
||||
|
||||
class _DispatchDescriptor(object):
|
||||
"""Class-level attributes on :class:`._Dispatch` classes."""
|
||||
|
||||
def __init__(self, fn):
|
||||
self.__name__ = fn.__name__
|
||||
self.__doc__ = fn.__doc__
|
||||
self._clslevel = weakref.WeakKeyDictionary()
|
||||
self._empty_listeners = weakref.WeakKeyDictionary()
|
||||
|
||||
def _contains(self, cls, evt):
|
||||
return cls in self._clslevel and \
|
||||
evt in self._clslevel[cls]
|
||||
|
||||
def insert(self, obj, target, propagate):
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls is not target and cls not in self._clslevel:
|
||||
self.update_subclass(cls)
|
||||
else:
|
||||
if cls not in self._clslevel:
|
||||
self._clslevel[cls] = []
|
||||
self._clslevel[cls].insert(0, obj)
|
||||
|
||||
def append(self, obj, target, propagate):
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls is not target and cls not in self._clslevel:
|
||||
self.update_subclass(cls)
|
||||
else:
|
||||
if cls not in self._clslevel:
|
||||
self._clslevel[cls] = []
|
||||
self._clslevel[cls].append(obj)
|
||||
|
||||
def update_subclass(self, target):
|
||||
if target not in self._clslevel:
|
||||
self._clslevel[target] = []
|
||||
clslevel = self._clslevel[target]
|
||||
for cls in target.__mro__[1:]:
|
||||
if cls in self._clslevel:
|
||||
clslevel.extend([
|
||||
fn for fn
|
||||
in self._clslevel[cls]
|
||||
if fn not in clslevel
|
||||
])
|
||||
|
||||
def remove(self, obj, target):
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls in self._clslevel:
|
||||
self._clslevel[cls].remove(obj)
|
||||
|
||||
def clear(self):
|
||||
"""Clear all class level listeners"""
|
||||
|
||||
for dispatcher in self._clslevel.values():
|
||||
dispatcher[:] = []
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _DispatchDescriptor at the class level of
|
||||
a dispatcher, this returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
elif obj._parent_cls in self._empty_listeners:
|
||||
ret = self._empty_listeners[obj._parent_cls]
|
||||
else:
|
||||
self._empty_listeners[obj._parent_cls] = ret = \
|
||||
_EmptyListener(self, obj._parent_cls)
|
||||
# assigning it to __dict__ means
|
||||
# memoized for fast re-access. but more memory.
|
||||
obj.__dict__[self.__name__] = ret
|
||||
return ret
|
||||
|
||||
class _EmptyListener(object):
|
||||
"""Serves as a class-level interface to the events
|
||||
served by a _DispatchDescriptor, when there are no
|
||||
instance-level events present.
|
||||
|
||||
Is replaced by _ListenerCollection when instance-level
|
||||
events are added.
|
||||
|
||||
"""
|
||||
def __init__(self, parent, target_cls):
|
||||
if target_cls not in parent._clslevel:
|
||||
parent.update_subclass(target_cls)
|
||||
self.parent = parent
|
||||
self.parent_listeners = parent._clslevel[target_cls]
|
||||
self.name = parent.__name__
|
||||
self.propagate = frozenset()
|
||||
self.listeners = ()
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _EmptyListener at the instance level of
|
||||
a dispatcher, this generates a new
|
||||
_ListenerCollection, applies it to the instance,
|
||||
and returns it.
|
||||
|
||||
"""
|
||||
obj.__dict__[self.name] = result = _ListenerCollection(
|
||||
self.parent, obj._parent_cls)
|
||||
return result
|
||||
|
||||
def _needs_modify(self, *args, **kw):
|
||||
raise NotImplementedError("need to call for_modify()")
|
||||
|
||||
exec_once = insert = append = remove = clear = _needs_modify
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
"""Execute this event."""
|
||||
|
||||
for fn in self.parent_listeners:
|
||||
fn(*args, **kw)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.parent_listeners)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return (self.parent_listeners)[index]
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.parent_listeners)
|
||||
|
||||
|
||||
class _ListenerCollection(object):
|
||||
"""Instance-level attributes on instances of :class:`._Dispatch`.
|
||||
|
||||
Represents a collection of listeners.
|
||||
|
||||
As of 0.7.9, _ListenerCollection is only first
|
||||
created via the _EmptyListener.for_modify() method.
|
||||
|
||||
"""
|
||||
|
||||
_exec_once = False
|
||||
|
||||
def __init__(self, parent, target_cls):
|
||||
if target_cls not in parent._clslevel:
|
||||
parent.update_subclass(target_cls)
|
||||
self.parent_listeners = parent._clslevel[target_cls]
|
||||
self.name = parent.__name__
|
||||
self.listeners = []
|
||||
self.propagate = set()
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _ListenerCollection at the instance level of
|
||||
a dispatcher, this returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
def exec_once(self, *args, **kw):
|
||||
"""Execute this event, but only if it has not been
|
||||
executed already for this collection."""
|
||||
|
||||
if not self._exec_once:
|
||||
self(*args, **kw)
|
||||
self._exec_once = True
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
"""Execute this event."""
|
||||
|
||||
for fn in self.parent_listeners:
|
||||
fn(*args, **kw)
|
||||
for fn in self.listeners:
|
||||
fn(*args, **kw)
|
||||
|
||||
# I'm not entirely thrilled about the overhead here,
|
||||
# but this allows class-level listeners to be added
|
||||
# at any point.
|
||||
#
|
||||
# In the absense of instance-level listeners,
|
||||
# we stay with the _EmptyListener object when called
|
||||
# at the instance level.
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners + self.listeners)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.parent_listeners + self.listeners)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return (self.parent_listeners + self.listeners)[index]
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.listeners or self.parent_listeners)
|
||||
|
||||
def _update(self, other, only_propagate=True):
|
||||
"""Populate from the listeners in another :class:`_Dispatch`
|
||||
object."""
|
||||
|
||||
existing_listeners = self.listeners
|
||||
existing_listener_set = set(existing_listeners)
|
||||
self.propagate.update(other.propagate)
|
||||
existing_listeners.extend([l for l
|
||||
in other.listeners
|
||||
if l not in existing_listener_set
|
||||
and not only_propagate or l in self.propagate
|
||||
])
|
||||
|
||||
def insert(self, obj, target, propagate):
|
||||
if obj not in self.listeners:
|
||||
self.listeners.insert(0, obj)
|
||||
if propagate:
|
||||
self.propagate.add(obj)
|
||||
|
||||
def append(self, obj, target, propagate):
|
||||
if obj not in self.listeners:
|
||||
self.listeners.append(obj)
|
||||
if propagate:
|
||||
self.propagate.add(obj)
|
||||
|
||||
def remove(self, obj, target):
|
||||
if obj in self.listeners:
|
||||
self.listeners.remove(obj)
|
||||
self.propagate.discard(obj)
|
||||
|
||||
def clear(self):
|
||||
self.listeners[:] = []
|
||||
self.propagate.clear()
|
||||
|
||||
class dispatcher(object):
|
||||
"""Descriptor used by target classes to
|
||||
deliver the _Dispatch class at the class level
|
||||
and produce new _Dispatch instances for target
|
||||
instances.
|
||||
|
||||
"""
|
||||
def __init__(self, events):
|
||||
self.dispatch_cls = events.dispatch
|
||||
self.events = events
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self.dispatch_cls
|
||||
obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
|
||||
return disp
|
||||
@@ -0,0 +1,10 @@
|
||||
# event/__init__.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
from .api import CANCEL, NO_RETVAL, listen, listens_for, remove, contains
|
||||
from .base import Events, dispatcher
|
||||
from .attr import RefCollection
|
||||
from .legacy import _legacy_signature
|
||||
@@ -0,0 +1,107 @@
|
||||
# event/api.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Public API functions for the event system.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .. import util, exc
|
||||
from .base import _registrars
|
||||
from .registry import _EventKey
|
||||
|
||||
CANCEL = util.symbol('CANCEL')
|
||||
NO_RETVAL = util.symbol('NO_RETVAL')
|
||||
|
||||
|
||||
def _event_key(target, identifier, fn):
|
||||
for evt_cls in _registrars[identifier]:
|
||||
tgt = evt_cls._accept_with(target)
|
||||
if tgt is not None:
|
||||
return _EventKey(target, identifier, fn, tgt)
|
||||
else:
|
||||
raise exc.InvalidRequestError("No such event '%s' for target '%s'" %
|
||||
(identifier, target))
|
||||
|
||||
def listen(target, identifier, fn, *args, **kw):
|
||||
"""Register a listener function for the given target.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
def unique_constraint_name(const, table):
|
||||
const.name = "uq_%s_%s" % (
|
||||
table.name,
|
||||
list(const.columns)[0].name
|
||||
)
|
||||
event.listen(
|
||||
UniqueConstraint,
|
||||
"after_parent_attach",
|
||||
unique_constraint_name)
|
||||
|
||||
"""
|
||||
|
||||
_event_key(target, identifier, fn).listen(*args, **kw)
|
||||
|
||||
|
||||
def listens_for(target, identifier, *args, **kw):
|
||||
"""Decorate a function as a listener for the given target + identifier.
|
||||
|
||||
e.g.::
|
||||
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.schema import UniqueConstraint
|
||||
|
||||
@event.listens_for(UniqueConstraint, "after_parent_attach")
|
||||
def unique_constraint_name(const, table):
|
||||
const.name = "uq_%s_%s" % (
|
||||
table.name,
|
||||
list(const.columns)[0].name
|
||||
)
|
||||
"""
|
||||
def decorate(fn):
|
||||
listen(target, identifier, fn, *args, **kw)
|
||||
return fn
|
||||
return decorate
|
||||
|
||||
|
||||
def remove(target, identifier, fn):
|
||||
"""Remove an event listener.
|
||||
|
||||
The arguments here should match exactly those which were sent to
|
||||
:func:`.listen`; all the event registration which proceeded as a result
|
||||
of this call will be reverted by calling :func:`.remove` with the same
|
||||
arguments.
|
||||
|
||||
e.g.::
|
||||
|
||||
# if a function was registered like this...
|
||||
@event.listens_for(SomeMappedClass, "before_insert", propagate=True)
|
||||
def my_listener_function(*arg):
|
||||
pass
|
||||
|
||||
# ... it's removed like this
|
||||
event.remove(SomeMappedClass, "before_insert", my_listener_function)
|
||||
|
||||
Above, the listener function associated with ``SomeMappedClass`` was also
|
||||
propagated to subclasses of ``SomeMappedClass``; the :func:`.remove` function
|
||||
will revert all of these operations.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
"""
|
||||
_event_key(target, identifier, fn).remove()
|
||||
|
||||
def contains(target, identifier, fn):
|
||||
"""Return True if the given target/ident/fn is set up to listen.
|
||||
|
||||
.. versionadded:: 0.9.0
|
||||
|
||||
"""
|
||||
|
||||
return _event_key(target, identifier, fn).contains()
|
||||
@@ -0,0 +1,376 @@
|
||||
# event/attr.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Attribute implementation for _Dispatch classes.
|
||||
|
||||
The various listener targets for a particular event class are represented
|
||||
as attributes, which refer to collections of listeners to be fired off.
|
||||
These collections can exist at the class level as well as at the instance
|
||||
level. An event is fired off using code like this::
|
||||
|
||||
some_object.dispatch.first_connect(arg1, arg2)
|
||||
|
||||
Above, ``some_object.dispatch`` would be an instance of ``_Dispatch`` and
|
||||
``first_connect`` is typically an instance of ``_ListenerCollection``
|
||||
if event listeners are present, or ``_EmptyListener`` if none are present.
|
||||
|
||||
The attribute mechanics here spend effort trying to ensure listener functions
|
||||
are available with a minimum of function call overhead, that unnecessary
|
||||
objects aren't created (i.e. many empty per-instance listener collections),
|
||||
as well as that everything is garbage collectable when owning references are
|
||||
lost. Other features such as "propagation" of listener functions across
|
||||
many ``_Dispatch`` instances, "joining" of multiple ``_Dispatch`` instances,
|
||||
as well as support for subclass propagation (e.g. events assigned to
|
||||
``Pool`` vs. ``QueuePool``) are all implemented here.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .. import util
|
||||
from . import registry
|
||||
from . import legacy
|
||||
from itertools import chain
|
||||
import weakref
|
||||
|
||||
class RefCollection(object):
|
||||
@util.memoized_property
|
||||
def ref(self):
|
||||
return weakref.ref(self, registry._collection_gced)
|
||||
|
||||
class _DispatchDescriptor(RefCollection):
|
||||
"""Class-level attributes on :class:`._Dispatch` classes."""
|
||||
|
||||
def __init__(self, parent_dispatch_cls, fn):
|
||||
self.__name__ = fn.__name__
|
||||
argspec = util.inspect_getargspec(fn)
|
||||
self.arg_names = argspec.args[1:]
|
||||
self.has_kw = bool(argspec.keywords)
|
||||
self.legacy_signatures = list(reversed(
|
||||
sorted(
|
||||
getattr(fn, '_legacy_signatures', []),
|
||||
key=lambda s: s[0]
|
||||
)
|
||||
))
|
||||
self.__doc__ = fn.__doc__ = legacy._augment_fn_docs(
|
||||
self, parent_dispatch_cls, fn)
|
||||
|
||||
self._clslevel = weakref.WeakKeyDictionary()
|
||||
self._empty_listeners = weakref.WeakKeyDictionary()
|
||||
|
||||
def _adjust_fn_spec(self, fn, named):
|
||||
if named:
|
||||
fn = self._wrap_fn_for_kw(fn)
|
||||
if self.legacy_signatures:
|
||||
try:
|
||||
argspec = util.get_callable_argspec(fn, no_self=True)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
fn = legacy._wrap_fn_for_legacy(self, fn, argspec)
|
||||
return fn
|
||||
|
||||
def _wrap_fn_for_kw(self, fn):
|
||||
def wrap_kw(*args, **kw):
|
||||
argdict = dict(zip(self.arg_names, args))
|
||||
argdict.update(kw)
|
||||
return fn(**argdict)
|
||||
return wrap_kw
|
||||
|
||||
|
||||
def insert(self, event_key, propagate):
|
||||
target = event_key.dispatch_target
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls is not target and cls not in self._clslevel:
|
||||
self.update_subclass(cls)
|
||||
else:
|
||||
if cls not in self._clslevel:
|
||||
self._clslevel[cls] = []
|
||||
self._clslevel[cls].insert(0, event_key._listen_fn)
|
||||
registry._stored_in_collection(event_key, self)
|
||||
|
||||
def append(self, event_key, propagate):
|
||||
target = event_key.dispatch_target
|
||||
assert isinstance(target, type), \
|
||||
"Class-level Event targets must be classes."
|
||||
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls is not target and cls not in self._clslevel:
|
||||
self.update_subclass(cls)
|
||||
else:
|
||||
if cls not in self._clslevel:
|
||||
self._clslevel[cls] = []
|
||||
self._clslevel[cls].append(event_key._listen_fn)
|
||||
registry._stored_in_collection(event_key, self)
|
||||
|
||||
def update_subclass(self, target):
|
||||
if target not in self._clslevel:
|
||||
self._clslevel[target] = []
|
||||
clslevel = self._clslevel[target]
|
||||
for cls in target.__mro__[1:]:
|
||||
if cls in self._clslevel:
|
||||
clslevel.extend([
|
||||
fn for fn
|
||||
in self._clslevel[cls]
|
||||
if fn not in clslevel
|
||||
])
|
||||
|
||||
def remove(self, event_key):
|
||||
target = event_key.dispatch_target
|
||||
stack = [target]
|
||||
while stack:
|
||||
cls = stack.pop(0)
|
||||
stack.extend(cls.__subclasses__())
|
||||
if cls in self._clslevel:
|
||||
self._clslevel[cls].remove(event_key.fn)
|
||||
registry._removed_from_collection(event_key, self)
|
||||
|
||||
def clear(self):
|
||||
"""Clear all class level listeners"""
|
||||
|
||||
to_clear = set()
|
||||
for dispatcher in self._clslevel.values():
|
||||
to_clear.update(dispatcher)
|
||||
dispatcher[:] = []
|
||||
registry._clear(self, to_clear)
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _DispatchDescriptor at the class level of
|
||||
a dispatcher, this returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
elif obj._parent_cls in self._empty_listeners:
|
||||
ret = self._empty_listeners[obj._parent_cls]
|
||||
else:
|
||||
self._empty_listeners[obj._parent_cls] = ret = \
|
||||
_EmptyListener(self, obj._parent_cls)
|
||||
# assigning it to __dict__ means
|
||||
# memoized for fast re-access. but more memory.
|
||||
obj.__dict__[self.__name__] = ret
|
||||
return ret
|
||||
|
||||
class _HasParentDispatchDescriptor(object):
|
||||
def _adjust_fn_spec(self, fn, named):
|
||||
return self.parent._adjust_fn_spec(fn, named)
|
||||
|
||||
class _EmptyListener(_HasParentDispatchDescriptor):
|
||||
"""Serves as a class-level interface to the events
|
||||
served by a _DispatchDescriptor, when there are no
|
||||
instance-level events present.
|
||||
|
||||
Is replaced by _ListenerCollection when instance-level
|
||||
events are added.
|
||||
|
||||
"""
|
||||
def __init__(self, parent, target_cls):
|
||||
if target_cls not in parent._clslevel:
|
||||
parent.update_subclass(target_cls)
|
||||
self.parent = parent # _DispatchDescriptor
|
||||
self.parent_listeners = parent._clslevel[target_cls]
|
||||
self.name = parent.__name__
|
||||
self.propagate = frozenset()
|
||||
self.listeners = ()
|
||||
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _EmptyListener at the instance level of
|
||||
a dispatcher, this generates a new
|
||||
_ListenerCollection, applies it to the instance,
|
||||
and returns it.
|
||||
|
||||
"""
|
||||
result = _ListenerCollection(self.parent, obj._parent_cls)
|
||||
if obj.__dict__[self.name] is self:
|
||||
obj.__dict__[self.name] = result
|
||||
return result
|
||||
|
||||
def _needs_modify(self, *args, **kw):
|
||||
raise NotImplementedError("need to call for_modify()")
|
||||
|
||||
exec_once = insert = append = remove = clear = _needs_modify
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
"""Execute this event."""
|
||||
|
||||
for fn in self.parent_listeners:
|
||||
fn(*args, **kw)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.parent_listeners)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.parent_listeners)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
|
||||
class _CompoundListener(_HasParentDispatchDescriptor):
|
||||
_exec_once = False
|
||||
|
||||
def exec_once(self, *args, **kw):
|
||||
"""Execute this event, but only if it has not been
|
||||
executed already for this collection."""
|
||||
|
||||
if not self._exec_once:
|
||||
self(*args, **kw)
|
||||
self._exec_once = True
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
"""Execute this event."""
|
||||
|
||||
for fn in self.parent_listeners:
|
||||
fn(*args, **kw)
|
||||
for fn in self.listeners:
|
||||
fn(*args, **kw)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parent_listeners) + len(self.listeners)
|
||||
|
||||
def __iter__(self):
|
||||
return chain(self.parent_listeners, self.listeners)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.listeners or self.parent_listeners)
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
class _ListenerCollection(RefCollection, _CompoundListener):
|
||||
"""Instance-level attributes on instances of :class:`._Dispatch`.
|
||||
|
||||
Represents a collection of listeners.
|
||||
|
||||
As of 0.7.9, _ListenerCollection is only first
|
||||
created via the _EmptyListener.for_modify() method.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, parent, target_cls):
|
||||
if target_cls not in parent._clslevel:
|
||||
parent.update_subclass(target_cls)
|
||||
self.parent_listeners = parent._clslevel[target_cls]
|
||||
self.parent = parent
|
||||
self.name = parent.__name__
|
||||
self.listeners = []
|
||||
self.propagate = set()
|
||||
|
||||
def for_modify(self, obj):
|
||||
"""Return an event collection which can be modified.
|
||||
|
||||
For _ListenerCollection at the instance level of
|
||||
a dispatcher, this returns self.
|
||||
|
||||
"""
|
||||
return self
|
||||
|
||||
def _update(self, other, only_propagate=True):
|
||||
"""Populate from the listeners in another :class:`_Dispatch`
|
||||
object."""
|
||||
|
||||
existing_listeners = self.listeners
|
||||
existing_listener_set = set(existing_listeners)
|
||||
self.propagate.update(other.propagate)
|
||||
other_listeners = [l for l
|
||||
in other.listeners
|
||||
if l not in existing_listener_set
|
||||
and not only_propagate or l in self.propagate
|
||||
]
|
||||
|
||||
existing_listeners.extend(other_listeners)
|
||||
|
||||
to_associate = other.propagate.union(other_listeners)
|
||||
registry._stored_in_collection_multi(self, other, to_associate)
|
||||
|
||||
def insert(self, event_key, propagate):
|
||||
if event_key._listen_fn not in self.listeners:
|
||||
event_key.prepend_to_list(self, self.listeners)
|
||||
if propagate:
|
||||
self.propagate.add(event_key._listen_fn)
|
||||
|
||||
def append(self, event_key, propagate):
|
||||
if event_key._listen_fn not in self.listeners:
|
||||
event_key.append_to_list(self, self.listeners)
|
||||
if propagate:
|
||||
self.propagate.add(event_key._listen_fn)
|
||||
|
||||
def remove(self, event_key):
|
||||
self.listeners.remove(event_key._listen_fn)
|
||||
self.propagate.discard(event_key._listen_fn)
|
||||
registry._removed_from_collection(event_key, self)
|
||||
|
||||
def clear(self):
|
||||
registry._clear(self, self.listeners)
|
||||
self.propagate.clear()
|
||||
self.listeners[:] = []
|
||||
|
||||
|
||||
class _JoinedDispatchDescriptor(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self
|
||||
else:
|
||||
obj.__dict__[self.name] = ret = _JoinedListener(
|
||||
obj.parent, self.name,
|
||||
getattr(obj.local, self.name)
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
class _JoinedListener(_CompoundListener):
|
||||
_exec_once = False
|
||||
|
||||
def __init__(self, parent, name, local):
|
||||
self.parent = parent
|
||||
self.name = name
|
||||
self.local = local
|
||||
self.parent_listeners = self.local
|
||||
|
||||
@property
|
||||
def listeners(self):
|
||||
return getattr(self.parent, self.name)
|
||||
|
||||
def _adjust_fn_spec(self, fn, named):
|
||||
return self.local._adjust_fn_spec(fn, named)
|
||||
|
||||
def for_modify(self, obj):
|
||||
self.local = self.parent_listeners = self.local.for_modify(obj)
|
||||
return self
|
||||
|
||||
def insert(self, event_key, propagate):
|
||||
self.local.insert(event_key, propagate)
|
||||
|
||||
def append(self, event_key, propagate):
|
||||
self.local.append(event_key, propagate)
|
||||
|
||||
def remove(self, event_key):
|
||||
self.local.remove(event_key)
|
||||
|
||||
def clear(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
# event/base.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Base implementation classes.
|
||||
|
||||
The public-facing ``Events`` serves as the base class for an event interface;
|
||||
it's public attributes represent different kinds of events. These attributes
|
||||
are mirrored onto a ``_Dispatch`` class, which serves as a container for
|
||||
collections of listener functions. These collections are represented both
|
||||
at the class level of a particular ``_Dispatch`` class as well as within
|
||||
instances of ``_Dispatch``.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .. import util
|
||||
from .attr import _JoinedDispatchDescriptor, _EmptyListener, _DispatchDescriptor
|
||||
|
||||
_registrars = util.defaultdict(list)
|
||||
|
||||
|
||||
def _is_event_name(name):
|
||||
return not name.startswith('_') and name != 'dispatch'
|
||||
|
||||
|
||||
class _UnpickleDispatch(object):
|
||||
"""Serializable callable that re-generates an instance of
|
||||
:class:`_Dispatch` given a particular :class:`.Events` subclass.
|
||||
|
||||
"""
|
||||
def __call__(self, _parent_cls):
|
||||
for cls in _parent_cls.__mro__:
|
||||
if 'dispatch' in cls.__dict__:
|
||||
return cls.__dict__['dispatch'].dispatch_cls(_parent_cls)
|
||||
else:
|
||||
raise AttributeError("No class with a 'dispatch' member present.")
|
||||
|
||||
|
||||
class _Dispatch(object):
|
||||
"""Mirror the event listening definitions of an Events class with
|
||||
listener collections.
|
||||
|
||||
Classes which define a "dispatch" member will return a
|
||||
non-instantiated :class:`._Dispatch` subclass when the member
|
||||
is accessed at the class level. When the "dispatch" member is
|
||||
accessed at the instance level of its owner, an instance
|
||||
of the :class:`._Dispatch` class is returned.
|
||||
|
||||
A :class:`._Dispatch` class is generated for each :class:`.Events`
|
||||
class defined, by the :func:`._create_dispatcher_class` function.
|
||||
The original :class:`.Events` classes remain untouched.
|
||||
This decouples the construction of :class:`.Events` subclasses from
|
||||
the implementation used by the event internals, and allows
|
||||
inspecting tools like Sphinx to work in an unsurprising
|
||||
way against the public API.
|
||||
|
||||
"""
|
||||
|
||||
_events = None
|
||||
"""reference the :class:`.Events` class which this
|
||||
:class:`._Dispatch` is created for."""
|
||||
|
||||
def __init__(self, _parent_cls):
|
||||
self._parent_cls = _parent_cls
|
||||
|
||||
@util.classproperty
|
||||
def _listen(cls):
|
||||
return cls._events._listen
|
||||
|
||||
def _join(self, other):
|
||||
"""Create a 'join' of this :class:`._Dispatch` and another.
|
||||
|
||||
This new dispatcher will dispatch events to both
|
||||
:class:`._Dispatch` objects.
|
||||
|
||||
"""
|
||||
if '_joined_dispatch_cls' not in self.__class__.__dict__:
|
||||
cls = type(
|
||||
"Joined%s" % self.__class__.__name__,
|
||||
(_JoinedDispatcher, self.__class__), {}
|
||||
)
|
||||
for ls in _event_descriptors(self):
|
||||
setattr(cls, ls.name, _JoinedDispatchDescriptor(ls.name))
|
||||
|
||||
self.__class__._joined_dispatch_cls = cls
|
||||
return self._joined_dispatch_cls(self, other)
|
||||
|
||||
def __reduce__(self):
|
||||
return _UnpickleDispatch(), (self._parent_cls, )
|
||||
|
||||
def _update(self, other, only_propagate=True):
|
||||
"""Populate from the listeners in another :class:`_Dispatch`
|
||||
object."""
|
||||
|
||||
for ls in _event_descriptors(other):
|
||||
if isinstance(ls, _EmptyListener):
|
||||
continue
|
||||
getattr(self, ls.name).\
|
||||
for_modify(self)._update(ls, only_propagate=only_propagate)
|
||||
|
||||
@util.hybridmethod
|
||||
def _clear(self):
|
||||
for attr in dir(self):
|
||||
if _is_event_name(attr):
|
||||
getattr(self, attr).for_modify(self).clear()
|
||||
|
||||
|
||||
def _event_descriptors(target):
|
||||
return [getattr(target, k) for k in dir(target) if _is_event_name(k)]
|
||||
|
||||
|
||||
class _EventMeta(type):
|
||||
"""Intercept new Event subclasses and create
|
||||
associated _Dispatch classes."""
|
||||
|
||||
def __init__(cls, classname, bases, dict_):
|
||||
_create_dispatcher_class(cls, classname, bases, dict_)
|
||||
return type.__init__(cls, classname, bases, dict_)
|
||||
|
||||
|
||||
def _create_dispatcher_class(cls, classname, bases, dict_):
|
||||
"""Create a :class:`._Dispatch` class corresponding to an
|
||||
:class:`.Events` class."""
|
||||
|
||||
# there's all kinds of ways to do this,
|
||||
# i.e. make a Dispatch class that shares the '_listen' method
|
||||
# of the Event class, this is the straight monkeypatch.
|
||||
dispatch_base = getattr(cls, 'dispatch', _Dispatch)
|
||||
dispatch_cls = type("%sDispatch" % classname,
|
||||
(dispatch_base, ), {})
|
||||
cls._set_dispatch(cls, dispatch_cls)
|
||||
|
||||
for k in dict_:
|
||||
if _is_event_name(k):
|
||||
setattr(dispatch_cls, k, _DispatchDescriptor(cls, dict_[k]))
|
||||
_registrars[k].append(cls)
|
||||
|
||||
if getattr(cls, '_dispatch_target', None):
|
||||
cls._dispatch_target.dispatch = dispatcher(cls)
|
||||
|
||||
|
||||
def _remove_dispatcher(cls):
|
||||
for k in dir(cls):
|
||||
if _is_event_name(k):
|
||||
_registrars[k].remove(cls)
|
||||
if not _registrars[k]:
|
||||
del _registrars[k]
|
||||
|
||||
class Events(util.with_metaclass(_EventMeta, object)):
|
||||
"""Define event listening functions for a particular target type."""
|
||||
|
||||
@staticmethod
|
||||
def _set_dispatch(cls, dispatch_cls):
|
||||
# this allows an Events subclass to define additional utility
|
||||
# methods made available to the target via
|
||||
# "self.dispatch._events.<utilitymethod>"
|
||||
# @staticemethod to allow easy "super" calls while in a metaclass
|
||||
# constructor.
|
||||
cls.dispatch = dispatch_cls
|
||||
dispatch_cls._events = cls
|
||||
|
||||
|
||||
@classmethod
|
||||
def _accept_with(cls, target):
|
||||
# Mapper, ClassManager, Session override this to
|
||||
# also accept classes, scoped_sessions, sessionmakers, etc.
|
||||
if hasattr(target, 'dispatch') and (
|
||||
isinstance(target.dispatch, cls.dispatch) or \
|
||||
isinstance(target.dispatch, type) and \
|
||||
issubclass(target.dispatch, cls.dispatch)
|
||||
):
|
||||
return target
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _listen(cls, event_key, propagate=False, insert=False, named=False):
|
||||
event_key.base_listen(propagate=propagate, insert=insert, named=named)
|
||||
|
||||
@classmethod
|
||||
def _remove(cls, event_key):
|
||||
event_key.remove()
|
||||
|
||||
@classmethod
|
||||
def _clear(cls):
|
||||
cls.dispatch._clear()
|
||||
|
||||
|
||||
class _JoinedDispatcher(object):
|
||||
"""Represent a connection between two _Dispatch objects."""
|
||||
|
||||
def __init__(self, local, parent):
|
||||
self.local = local
|
||||
self.parent = parent
|
||||
self._parent_cls = local._parent_cls
|
||||
|
||||
|
||||
class dispatcher(object):
|
||||
"""Descriptor used by target classes to
|
||||
deliver the _Dispatch class at the class level
|
||||
and produce new _Dispatch instances for target
|
||||
instances.
|
||||
|
||||
"""
|
||||
def __init__(self, events):
|
||||
self.dispatch_cls = events.dispatch
|
||||
self.events = events
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
if obj is None:
|
||||
return self.dispatch_cls
|
||||
obj.__dict__['dispatch'] = disp = self.dispatch_cls(cls)
|
||||
return disp
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# event/legacy.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Routines to handle adaption of legacy call signatures,
|
||||
generation of deprecation notes and docstrings.
|
||||
|
||||
"""
|
||||
|
||||
from .. import util
|
||||
|
||||
def _legacy_signature(since, argnames, converter=None):
|
||||
def leg(fn):
|
||||
if not hasattr(fn, '_legacy_signatures'):
|
||||
fn._legacy_signatures = []
|
||||
fn._legacy_signatures.append((since, argnames, converter))
|
||||
return fn
|
||||
return leg
|
||||
|
||||
def _wrap_fn_for_legacy(dispatch_descriptor, fn, argspec):
|
||||
for since, argnames, conv in dispatch_descriptor.legacy_signatures:
|
||||
if argnames[-1] == "**kw":
|
||||
has_kw = True
|
||||
argnames = argnames[0:-1]
|
||||
else:
|
||||
has_kw = False
|
||||
|
||||
if len(argnames) == len(argspec.args) \
|
||||
and has_kw is bool(argspec.keywords):
|
||||
|
||||
if conv:
|
||||
assert not has_kw
|
||||
def wrap_leg(*args):
|
||||
return fn(*conv(*args))
|
||||
else:
|
||||
def wrap_leg(*args, **kw):
|
||||
argdict = dict(zip(dispatch_descriptor.arg_names, args))
|
||||
args = [argdict[name] for name in argnames]
|
||||
if has_kw:
|
||||
return fn(*args, **kw)
|
||||
else:
|
||||
return fn(*args)
|
||||
return wrap_leg
|
||||
else:
|
||||
return fn
|
||||
|
||||
def _indent(text, indent):
|
||||
return "\n".join(
|
||||
indent + line
|
||||
for line in text.split("\n")
|
||||
)
|
||||
|
||||
def _standard_listen_example(dispatch_descriptor, sample_target, fn):
|
||||
example_kw_arg = _indent(
|
||||
"\n".join(
|
||||
"%(arg)s = kw['%(arg)s']" % {"arg": arg}
|
||||
for arg in dispatch_descriptor.arg_names[0:2]
|
||||
),
|
||||
" ")
|
||||
if dispatch_descriptor.legacy_signatures:
|
||||
current_since = max(since for since, args, conv
|
||||
in dispatch_descriptor.legacy_signatures)
|
||||
else:
|
||||
current_since = None
|
||||
text = (
|
||||
"from sqlalchemy import event\n\n"
|
||||
"# standard decorator style%(current_since)s\n"
|
||||
"@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
|
||||
"def receive_%(event_name)s(%(named_event_arguments)s%(has_kw_arguments)s):\n"
|
||||
" \"listen for the '%(event_name)s' event\"\n"
|
||||
"\n # ... (event handling logic) ...\n"
|
||||
)
|
||||
|
||||
if len(dispatch_descriptor.arg_names) > 2:
|
||||
text += (
|
||||
|
||||
"\n# named argument style (new in 0.9)\n"
|
||||
"@event.listens_for(%(sample_target)s, '%(event_name)s', named=True)\n"
|
||||
"def receive_%(event_name)s(**kw):\n"
|
||||
" \"listen for the '%(event_name)s' event\"\n"
|
||||
"%(example_kw_arg)s\n"
|
||||
"\n # ... (event handling logic) ...\n"
|
||||
)
|
||||
|
||||
text %= {
|
||||
"current_since": " (arguments as of %s)" %
|
||||
current_since if current_since else "",
|
||||
"event_name": fn.__name__,
|
||||
"has_kw_arguments": ", **kw" if dispatch_descriptor.has_kw else "",
|
||||
"named_event_arguments": ", ".join(dispatch_descriptor.arg_names),
|
||||
"example_kw_arg": example_kw_arg,
|
||||
"sample_target": sample_target
|
||||
}
|
||||
return text
|
||||
|
||||
def _legacy_listen_examples(dispatch_descriptor, sample_target, fn):
|
||||
text = ""
|
||||
for since, args, conv in dispatch_descriptor.legacy_signatures:
|
||||
text += (
|
||||
"\n# legacy calling style (pre-%(since)s)\n"
|
||||
"@event.listens_for(%(sample_target)s, '%(event_name)s')\n"
|
||||
"def receive_%(event_name)s(%(named_event_arguments)s%(has_kw_arguments)s):\n"
|
||||
" \"listen for the '%(event_name)s' event\"\n"
|
||||
"\n # ... (event handling logic) ...\n" % {
|
||||
"since": since,
|
||||
"event_name": fn.__name__,
|
||||
"has_kw_arguments": " **kw" if dispatch_descriptor.has_kw else "",
|
||||
"named_event_arguments": ", ".join(args),
|
||||
"sample_target": sample_target
|
||||
}
|
||||
)
|
||||
return text
|
||||
|
||||
def _version_signature_changes(dispatch_descriptor):
|
||||
since, args, conv = dispatch_descriptor.legacy_signatures[0]
|
||||
return (
|
||||
"\n.. versionchanged:: %(since)s\n"
|
||||
" The ``%(event_name)s`` event now accepts the \n"
|
||||
" arguments ``%(named_event_arguments)s%(has_kw_arguments)s``.\n"
|
||||
" Listener functions which accept the previous argument \n"
|
||||
" signature(s) listed above will be automatically \n"
|
||||
" adapted to the new signature." % {
|
||||
"since": since,
|
||||
"event_name": dispatch_descriptor.__name__,
|
||||
"named_event_arguments": ", ".join(dispatch_descriptor.arg_names),
|
||||
"has_kw_arguments": ", **kw" if dispatch_descriptor.has_kw else ""
|
||||
}
|
||||
)
|
||||
|
||||
def _augment_fn_docs(dispatch_descriptor, parent_dispatch_cls, fn):
|
||||
header = ".. container:: event_signatures\n\n"\
|
||||
" Example argument forms::\n"\
|
||||
"\n"
|
||||
|
||||
sample_target = getattr(parent_dispatch_cls, "_target_class_doc", "obj")
|
||||
text = (
|
||||
header +
|
||||
_indent(
|
||||
_standard_listen_example(
|
||||
dispatch_descriptor, sample_target, fn),
|
||||
" " * 8)
|
||||
)
|
||||
if dispatch_descriptor.legacy_signatures:
|
||||
text += _indent(
|
||||
_legacy_listen_examples(
|
||||
dispatch_descriptor, sample_target, fn),
|
||||
" " * 8)
|
||||
|
||||
text += _version_signature_changes(dispatch_descriptor)
|
||||
|
||||
return util.inject_docstring_text(fn.__doc__,
|
||||
text,
|
||||
1
|
||||
)
|
||||
@@ -0,0 +1,236 @@
|
||||
# event/registry.py
|
||||
# Copyright (C) 2005-2014 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides managed registration services on behalf of :func:`.listen`
|
||||
arguments.
|
||||
|
||||
By "managed registration", we mean that event listening functions and
|
||||
other objects can be added to various collections in such a way that their
|
||||
membership in all those collections can be revoked at once, based on
|
||||
an equivalent :class:`._EventKey`.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import weakref
|
||||
import collections
|
||||
import types
|
||||
from .. import exc
|
||||
|
||||
|
||||
_key_to_collection = collections.defaultdict(dict)
|
||||
"""
|
||||
Given an original listen() argument, can locate all
|
||||
listener collections and the listener fn contained
|
||||
|
||||
(target, identifier, fn) -> {
|
||||
ref(listenercollection) -> ref(listener_fn)
|
||||
ref(listenercollection) -> ref(listener_fn)
|
||||
ref(listenercollection) -> ref(listener_fn)
|
||||
}
|
||||
"""
|
||||
|
||||
_collection_to_key = collections.defaultdict(dict)
|
||||
"""
|
||||
Given a _ListenerCollection or _DispatchDescriptor, can locate
|
||||
all the original listen() arguments and the listener fn contained
|
||||
|
||||
ref(listenercollection) -> {
|
||||
ref(listener_fn) -> (target, identifier, fn),
|
||||
ref(listener_fn) -> (target, identifier, fn),
|
||||
ref(listener_fn) -> (target, identifier, fn),
|
||||
}
|
||||
"""
|
||||
|
||||
def _collection_gced(ref):
|
||||
# defaultdict, so can't get a KeyError
|
||||
if not _collection_to_key or ref not in _collection_to_key:
|
||||
return
|
||||
listener_to_key = _collection_to_key.pop(ref)
|
||||
for key in listener_to_key.values():
|
||||
if key in _key_to_collection:
|
||||
# defaultdict, so can't get a KeyError
|
||||
dispatch_reg = _key_to_collection[key]
|
||||
dispatch_reg.pop(ref)
|
||||
if not dispatch_reg:
|
||||
_key_to_collection.pop(key)
|
||||
|
||||
def _stored_in_collection(event_key, owner):
|
||||
key = event_key._key
|
||||
|
||||
dispatch_reg = _key_to_collection[key]
|
||||
|
||||
owner_ref = owner.ref
|
||||
listen_ref = weakref.ref(event_key._listen_fn)
|
||||
|
||||
if owner_ref in dispatch_reg:
|
||||
assert dispatch_reg[owner_ref] == listen_ref
|
||||
else:
|
||||
dispatch_reg[owner_ref] = listen_ref
|
||||
|
||||
listener_to_key = _collection_to_key[owner_ref]
|
||||
listener_to_key[listen_ref] = key
|
||||
|
||||
def _removed_from_collection(event_key, owner):
|
||||
key = event_key._key
|
||||
|
||||
dispatch_reg = _key_to_collection[key]
|
||||
|
||||
listen_ref = weakref.ref(event_key._listen_fn)
|
||||
|
||||
owner_ref = owner.ref
|
||||
dispatch_reg.pop(owner_ref, None)
|
||||
if not dispatch_reg:
|
||||
del _key_to_collection[key]
|
||||
|
||||
if owner_ref in _collection_to_key:
|
||||
listener_to_key = _collection_to_key[owner_ref]
|
||||
listener_to_key.pop(listen_ref)
|
||||
|
||||
def _stored_in_collection_multi(newowner, oldowner, elements):
|
||||
if not elements:
|
||||
return
|
||||
|
||||
oldowner = oldowner.ref
|
||||
newowner = newowner.ref
|
||||
|
||||
old_listener_to_key = _collection_to_key[oldowner]
|
||||
new_listener_to_key = _collection_to_key[newowner]
|
||||
|
||||
for listen_fn in elements:
|
||||
listen_ref = weakref.ref(listen_fn)
|
||||
key = old_listener_to_key[listen_ref]
|
||||
dispatch_reg = _key_to_collection[key]
|
||||
if newowner in dispatch_reg:
|
||||
assert dispatch_reg[newowner] == listen_ref
|
||||
else:
|
||||
dispatch_reg[newowner] = listen_ref
|
||||
|
||||
new_listener_to_key[listen_ref] = key
|
||||
|
||||
def _clear(owner, elements):
|
||||
if not elements:
|
||||
return
|
||||
|
||||
owner = owner.ref
|
||||
listener_to_key = _collection_to_key[owner]
|
||||
for listen_fn in elements:
|
||||
listen_ref = weakref.ref(listen_fn)
|
||||
key = listener_to_key[listen_ref]
|
||||
dispatch_reg = _key_to_collection[key]
|
||||
dispatch_reg.pop(owner, None)
|
||||
|
||||
if not dispatch_reg:
|
||||
del _key_to_collection[key]
|
||||
|
||||
|
||||
class _EventKey(object):
|
||||
"""Represent :func:`.listen` arguments.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, target, identifier, fn, dispatch_target, _fn_wrap=None):
|
||||
self.target = target
|
||||
self.identifier = identifier
|
||||
self.fn = fn
|
||||
if isinstance(fn, types.MethodType):
|
||||
self.fn_key = id(fn.__func__), id(fn.__self__)
|
||||
else:
|
||||
self.fn_key = id(fn)
|
||||
self.fn_wrap = _fn_wrap
|
||||
self.dispatch_target = dispatch_target
|
||||
|
||||
@property
|
||||
def _key(self):
|
||||
return (id(self.target), self.identifier, self.fn_key)
|
||||
|
||||
def with_wrapper(self, fn_wrap):
|
||||
if fn_wrap is self._listen_fn:
|
||||
return self
|
||||
else:
|
||||
return _EventKey(
|
||||
self.target,
|
||||
self.identifier,
|
||||
self.fn,
|
||||
self.dispatch_target,
|
||||
_fn_wrap=fn_wrap
|
||||
)
|
||||
|
||||
def with_dispatch_target(self, dispatch_target):
|
||||
if dispatch_target is self.dispatch_target:
|
||||
return self
|
||||
else:
|
||||
return _EventKey(
|
||||
self.target,
|
||||
self.identifier,
|
||||
self.fn,
|
||||
dispatch_target,
|
||||
_fn_wrap=self.fn_wrap
|
||||
)
|
||||
|
||||
def listen(self, *args, **kw):
|
||||
self.dispatch_target.dispatch._listen(self, *args, **kw)
|
||||
|
||||
def remove(self):
|
||||
key = self._key
|
||||
|
||||
if key not in _key_to_collection:
|
||||
raise exc.InvalidRequestError(
|
||||
"No listeners found for event %s / %r / %s " %
|
||||
(self.target, self.identifier, self.fn)
|
||||
)
|
||||
dispatch_reg = _key_to_collection.pop(key)
|
||||
|
||||
for collection_ref, listener_ref in dispatch_reg.items():
|
||||
collection = collection_ref()
|
||||
listener_fn = listener_ref()
|
||||
if collection is not None and listener_fn is not None:
|
||||
collection.remove(self.with_wrapper(listener_fn))
|
||||
|
||||
def contains(self):
|
||||
"""Return True if this event key is registered to listen.
|
||||
"""
|
||||
return self._key in _key_to_collection
|
||||
|
||||
def base_listen(self, propagate=False, insert=False,
|
||||
named=False):
|
||||
|
||||
target, identifier, fn = \
|
||||
self.dispatch_target, self.identifier, self._listen_fn
|
||||
|
||||
dispatch_descriptor = getattr(target.dispatch, identifier)
|
||||
|
||||
fn = dispatch_descriptor._adjust_fn_spec(fn, named)
|
||||
self = self.with_wrapper(fn)
|
||||
|
||||
if insert:
|
||||
dispatch_descriptor.\
|
||||
for_modify(target.dispatch).insert(self, propagate)
|
||||
else:
|
||||
dispatch_descriptor.\
|
||||
for_modify(target.dispatch).append(self, propagate)
|
||||
|
||||
@property
|
||||
def _listen_fn(self):
|
||||
return self.fn_wrap or self.fn
|
||||
|
||||
def append_value_to_list(self, owner, list_, value):
|
||||
_stored_in_collection(self, owner)
|
||||
list_.append(value)
|
||||
|
||||
def append_to_list(self, owner, list_):
|
||||
_stored_in_collection(self, owner)
|
||||
list_.append(self._listen_fn)
|
||||
|
||||
def remove_from_list(self, owner, list_):
|
||||
_removed_from_collection(self, owner)
|
||||
list_.remove(self._listen_fn)
|
||||
|
||||
def prepend_to_list(self, owner, list_):
|
||||
_stored_in_collection(self, owner)
|
||||
list_.insert(0, self._listen_fn)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user