launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #30484
[Merge] ~cjwatson/launchpad:remove-sqlbase into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:remove-sqlbase into launchpad:master.
Commit message:
Remove SQLBase
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/451833
It's finally unused. I also simplified `lp.services.database.sqlbase.quote` slightly in the process.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:remove-sqlbase into launchpad:master.
diff --git a/doc/reference/python.rst b/doc/reference/python.rst
index 5d877b3..a616784 100644
--- a/doc/reference/python.rst
+++ b/doc/reference/python.rst
@@ -264,21 +264,6 @@ passes and returns them easier to debug.
Database-related
================
-Storm
------
-
-We use two database ORM (object-relational mapper) APIs in Launchpad, the
-older and deprecated SQLObject API and the new and improved `Storm
-<https://storm.canonical.com>`_ API. All new code should use the Storm API,
-and you are encouraged to convert existing code to Storm as part of your
-tech-debt payments.
-
-.. note::
-
- The SQLObject and Storm ``ResultSet`` interfaces are not compatible, so
- e.g. if you need to ``UNION`` between these two, you will run into
- trouble. We are looking into ways to address this.
-
Field attributes
----------------
diff --git a/lib/lp/app/doc/batch-navigation.rst b/lib/lp/app/doc/batch-navigation.rst
index 25274f1..ab762aa 100644
--- a/lib/lp/app/doc/batch-navigation.rst
+++ b/lib/lp/app/doc/batch-navigation.rst
@@ -8,8 +8,7 @@ This documents and tests the Launchpad-specific elements of its usage.
Note that our use of the batching code relies on the registration of
lp.services.webapp.batching.FiniteSequenceAdapter for
-storm.zope.interfaces.IResultSet and
-storm.zope.interfaces.ISQLObjectResultSet.
+storm.zope.interfaces.IResultSet.
Batch navigation provides a way to navigate batch results in a web
page by providing URL links to the next, previous and numbered pages
diff --git a/lib/lp/blueprints/model/sprint.py b/lib/lp/blueprints/model/sprint.py
index 3a45f35..15f20ea 100644
--- a/lib/lp/blueprints/model/sprint.py
+++ b/lib/lp/blueprints/model/sprint.py
@@ -425,12 +425,7 @@ class HasSprintsMixin:
Subclasses must overwrite this method if it doesn't suit them.
"""
- try:
- table = getattr(self, "__storm_table__")
- except AttributeError:
- # XXX cjwatson 2020-09-10: Remove this once all inheritors have
- # been converted from SQLObject to Storm.
- table = getattr(self, "_table")
+ table = getattr(self, "__storm_table__")
return [
getattr(Specification, table.lower()) == self,
Specification.id == SprintSpecification.specification_id,
diff --git a/lib/lp/services/database/interfaces.py b/lib/lp/services/database/interfaces.py
index 808e25b..f619539 100644
--- a/lib/lp/services/database/interfaces.py
+++ b/lib/lp/services/database/interfaces.py
@@ -9,7 +9,6 @@ __all__ = [
"IPrimaryObject",
"IPrimaryStore",
"IRequestExpired",
- "ISQLBase",
"IStandbyStore",
"IStore",
"IStoreSelector",
@@ -21,7 +20,6 @@ __all__ = [
from zope.interface import Interface
from zope.interface.common.interfaces import IRuntimeError
-from zope.schema import Int
class IRequestExpired(IRuntimeError):
@@ -30,15 +28,6 @@ class IRequestExpired(IRuntimeError):
"""
-# XXX 2007-02-09 jamesh:
-# This derived from sqlos.interfaces.ISQLObject before hand. I don't
-# think it is ever used though ...
-class ISQLBase(Interface):
- """An extension of ISQLObject that provides an ID."""
-
- id = Int(title="The integer ID for the instance")
-
-
#
# Database policies
#
diff --git a/lib/lp/services/database/sqlbase.py b/lib/lp/services/database/sqlbase.py
index 3a2f903..d24dd95 100644
--- a/lib/lp/services/database/sqlbase.py
+++ b/lib/lp/services/database/sqlbase.py
@@ -19,16 +19,13 @@ __all__ = [
"quote_identifier",
"reset_store",
"session_store",
- "SQLBase",
"sqlvalues",
"StupidCache",
]
-
-from datetime import datetime, timezone
+from datetime import timezone
import psycopg2
-import storm
import transaction
from psycopg2.extensions import (
ISOLATION_LEVEL_AUTOCOMMIT,
@@ -41,27 +38,18 @@ from psycopg2.extensions import (
from storm.databases.postgres import compile as postgres_compile
from storm.expr import State
from storm.expr import compile as storm_compile
-from storm.locals import Storm # noqa: B1
-from storm.locals import Store
from storm.zope.interfaces import IZStorm
from twisted.python.util import mergeFunctionMetadata
from zope.component import getUtility
-from zope.interface import implementer
-from zope.security.proxy import removeSecurityProxy
from lp.services.config import dbconfig
from lp.services.database.interfaces import (
DEFAULT_FLAVOR,
MAIN_STORE,
DisallowedStore,
- IPrimaryObject,
- IPrimaryStore,
- ISQLBase,
- IStore,
IStoreSelector,
)
from lp.services.database.sqlobject import sqlrepr
-from lp.services.propertycache import clear_property_cache
# Default we want for scripts, and the PostgreSQL default. Note psycopg1 will
# use SERIALIZABLE unless we override, but psycopg2 will not.
@@ -108,168 +96,15 @@ class StupidCache:
return self._cache.keys()
-def _get_sqlobject_store():
- """Return the store used by the SQLObject compatibility layer."""
- return getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
-
-
-class LaunchpadStyle(storm.sqlobject.SQLObjectStyle):
- """A SQLObject style for launchpad.
+def _get_main_default_store():
+ """Return the main store using the default flavor.
- Python attributes and database columns are lowercase.
- Class names and database tables are MixedCase. Using this style should
- simplify SQLBase class definitions since more defaults will be correct.
+ For web requests, the default flavor uses a primary or standby database
+ depending on the type of request (see
+ `lp.services.database.policy.LaunchpadDatabasePolicy`); in all other
+ situations, it uses the primary database.
"""
-
- def pythonAttrToDBColumn(self, attr):
- return attr
-
- def dbColumnToPythonAttr(self, col):
- return col
-
- def pythonClassToDBTable(self, className):
- return className
-
- def dbTableToPythonClass(self, table):
- return table
-
- def idForTable(self, table):
- return "id"
-
- def pythonClassToAttr(self, className):
- return className.lower()
-
- # dsilvers: 20050322: If you take this method out; then RelativeJoin
- # instances in our SQLObject classes cause the following error:
- # AttributeError: 'LaunchpadStyle' object has no attribute
- # 'tableReference'
- def tableReference(self, table):
- """Return the tablename mapped for use in RelativeJoin statements."""
- return table.__str__()
-
-
-@implementer(ISQLBase)
-class SQLBase(storm.sqlobject.SQLObjectBase):
- """Base class emulating SQLObject for legacy database classes."""
-
- _style = LaunchpadStyle()
-
- # Silence warnings in linter script, which complains about all
- # SQLBase-derived objects missing an id.
- id = None
-
- def __init__(self, *args, **kwargs):
- """Extended version of the SQLObjectBase constructor.
-
- We force use of the primary Store.
-
- We refetch any parameters from different stores from the
- correct primary Store.
- """
- # Make it simple to write dumb-invalidators - initialized
- # _cached_properties to a valid list rather than just-in-time
- # creation.
- self._cached_properties = []
- store = IPrimaryStore(self.__class__)
-
- # The constructor will fail if objects from a different Store
- # are passed in. We need to refetch these objects from the correct
- # primary Store if necessary so the foreign key references can be
- # constructed.
- # XXX StuartBishop 2009-03-02 bug=336867: We probably want to remove
- # this code - there are enough other places developers have to be
- # aware of the replication # set boundaries. Why should
- # Person(..., account=an_account) work but
- # some_person.account = an_account fail?
- for key, argument in kwargs.items():
- argument = removeSecurityProxy(argument)
- if not isinstance(argument, Storm): # noqa: B1
- continue
- argument_store = Store.of(argument)
- if argument_store is not store:
- new_argument = store.find(
- argument.__class__, id=argument.id
- ).one()
- assert (
- new_argument is not None
- ), "%s not yet synced to this store" % repr(argument)
- kwargs[key] = new_argument
-
- store.add(self)
- try:
- self._create(None, **kwargs)
- except Exception:
- store.remove(self)
- raise
-
- @classmethod
- def _get_store(cls):
- return IStore(cls)
-
- def __repr__(self):
- return "<%s object>" % (self.__class__.__name__)
-
- def destroySelf(self):
- my_primary = IPrimaryObject(self)
- if self is my_primary:
- super().destroySelf()
- else:
- my_primary.destroySelf()
-
- def __eq__(self, other):
- """Equality operator.
-
- Objects compare equal if they have the same class and id, and the id
- is not None.
-
- This rule allows objects retrieved from different stores to compare
- equal. Newly-created objects may not yet have an id; in such cases
- we flush the store so that we can find out their id.
- """
- naked_self = removeSecurityProxy(self)
- naked_other = removeSecurityProxy(other)
- if naked_self.__class__ != naked_other.__class__:
- return False
- try:
- self_id = naked_self.id
- except KeyError:
- self.syncUpdate()
- self_id = naked_self.id
- if self_id is None:
- return False
- try:
- other_id = naked_other.id
- except KeyError:
- other.syncUpdate()
- other_id = naked_other.id
- return self_id == other_id
-
- def __ne__(self, other):
- """Inverse of __eq__."""
- return not (self == other)
-
- def __hash__(self):
- """Hash operator.
-
- We must define __hash__ since we define __eq__ (Python 3 requires
- this), but we need to take care to preserve the invariant that
- objects that compare equal have the same hash value. Newly-created
- objects may not yet have an id; in such cases we flush the store so
- that we can find out their id.
- """
- try:
- id = self.id
- except KeyError:
- self.syncUpdate()
- id = self.id
- return hash((self.__class__, id))
-
- def __storm_invalidated__(self):
- """Flush cached properties."""
- # XXX: RobertCollins 2010-08-16 bug=622648: Note this is not directly
- # tested, but the entire test suite blows up awesomely if it's broken.
- # It's entirely unclear where tests for this should be.
- clear_property_cache(self)
+ return getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
def get_transaction_timestamp(store):
@@ -314,22 +149,13 @@ def quote(x):
>>> from datetime import datetime, date, time
>>> quote(datetime(2003, 12, 4, 13, 45, 50))
- "'2003-12-04 13:45:50'"
+ "'2003-12-04T13:45:50'"
>>> quote(date(2003, 12, 4))
"'2003-12-04'"
>>> quote(time(13, 45, 50))
"'13:45:50'"
- This function special cases datetime objects, due to a bug that has
- since been fixed in SQLOS (it installed an SQLObject converter that
- stripped the time component from the value). By itself, the sqlrepr
- function has the following output:
-
- >>> sqlrepr(datetime(2003, 12, 4, 13, 45, 50), "postgres")
- "'2003-12-04T13:45:50'"
-
- This function also special cases set objects, which SQLObject's
- sqlrepr() doesn't know how to handle.
+ sqlrepr() also special-cases set objects.
>>> quote(set([1, 2, 3]))
'(1, 2, 3)'
@@ -337,14 +163,6 @@ def quote(x):
>>> quote(frozenset([1, 2, 3]))
'(1, 2, 3)'
"""
- if isinstance(x, datetime):
- return "'%s'" % x
- elif ISQLBase(x, None) is not None:
- return str(x.id)
- elif isinstance(x, (set, frozenset)):
- # SQLObject can't cope with sets, so convert to a list, which it
- # /does/ know how to handle.
- x = list(x)
return sqlrepr(x, "postgres")
@@ -532,7 +350,7 @@ def block_implicit_flushes(func):
def block_implicit_flushes_decorator(*args, **kwargs):
try:
- store = _get_sqlobject_store()
+ store = _get_main_default_store()
except DisallowedStore:
return func(*args, **kwargs)
store.block_implicit_flushes()
@@ -551,7 +369,7 @@ def reset_store(func):
try:
return func(*args, **kwargs)
finally:
- _get_sqlobject_store().reset()
+ _get_main_default_store().reset()
return mergeFunctionMetadata(func, reset_store_decorator)
@@ -603,7 +421,7 @@ class cursor:
"""
def __init__(self):
- self._connection = _get_sqlobject_store()._connection
+ self._connection = _get_main_default_store()._connection
self._result = None
def execute(self, query, params=None):
diff --git a/lib/lp/services/database/sqlobject/__init__.py b/lib/lp/services/database/sqlobject/__init__.py
index 13fabdc..bd5e645 100644
--- a/lib/lp/services/database/sqlobject/__init__.py
+++ b/lib/lp/services/database/sqlobject/__init__.py
@@ -7,30 +7,6 @@
import datetime
from storm.expr import SQL
-from storm.sqlobject import ( # noqa: F401
- AND,
- CONTAINSSTRING,
- DESC,
- IN,
- LIKE,
- NOT,
- OR,
- BoolCol,
- DateCol,
- FloatCol,
- ForeignKey,
- IntCol,
- IntervalCol,
- SingleJoin,
- SQLConstant,
- SQLMultipleJoin,
- SQLObjectBase,
- SQLObjectMoreThanOneResultError,
- SQLObjectResultSet,
- SQLRelatedJoin,
- StringCol,
- UtcDateTimeCol,
-)
_sqlStringReplace = [
("\\", "\\\\"),
@@ -69,7 +45,7 @@ def sqlrepr(value, dbname=None):
return repr(value)
elif value is None:
return "NULL"
- elif isinstance(value, (list, set, tuple)):
+ elif isinstance(value, (frozenset, list, set, tuple)):
return "(%s)" % ", ".join(sqlrepr(v, dbname) for v in value)
elif isinstance(value, datetime.datetime):
return value.strftime("'%Y-%m-%dT%H:%M:%S'")
diff --git a/lib/lp/services/webapp/configure.zcml b/lib/lp/services/webapp/configure.zcml
index ad25c53..5b61d74 100644
--- a/lib/lp/services/webapp/configure.zcml
+++ b/lib/lp/services/webapp/configure.zcml
@@ -50,10 +50,6 @@
factory='.batching.FiniteSequenceAdapter' />
<adapter
- factory='.batching.FiniteSequenceAdapter'
- for='storm.zope.interfaces.ISQLObjectResultSet' />
-
- <adapter
factory='.batching.BoundReferenceSetAdapter'
for='storm.references.BoundReferenceSet' />
@@ -246,10 +242,6 @@
<adapter
factory="lp.services.webapp.snapshot.snapshot_sql_result" />
- <!-- It also works for the legacy SQLObject interface. -->
- <adapter
- factory="lp.services.webapp.snapshot.snapshot_sql_result"
- for="storm.zope.interfaces.ISQLObjectResultSet" />
<class class="lp.services.webapp.publisher.RenamedView">
<allow interface="zope.publisher.interfaces.browser.IBrowserPublisher"
diff --git a/lib/lp/services/webapp/database.zcml b/lib/lp/services/webapp/database.zcml
index b7130a2..7a5ae12 100644
--- a/lib/lp/services/webapp/database.zcml
+++ b/lib/lp/services/webapp/database.zcml
@@ -60,9 +60,6 @@
<implements interface="lp.services.database.interfaces.IStore" />
<allow attributes="get" />
</class>
- <class class="lp.services.database.sqlbase.SQLBase">
- <implements interface="lp.services.database.interfaces.IDBObject" />
- </class>
<class class="lp.services.database.stormbase.StormBase">
<implements interface="lp.services.database.interfaces.IDBObject" />
</class>
diff --git a/lib/lp/services/webapp/snapshot.py b/lib/lp/services/webapp/snapshot.py
index 2d46805..fd6aaa9 100644
--- a/lib/lp/services/webapp/snapshot.py
+++ b/lib/lp/services/webapp/snapshot.py
@@ -19,13 +19,12 @@ HARD_LIMIT_FOR_SNAPSHOT = 1000
@implementer(ISnapshotValueFactory)
-@adapter(IResultSet) # And ISQLObjectResultSet.
+@adapter(IResultSet)
def snapshot_sql_result(value):
"""Snapshot adapter for the Storm result set."""
- # SQLMultipleJoin and SQLRelatedJoin return
- # SelectResults, which doesn't really help the Snapshot
- # object. We therefore list()ify the values; this isn't
- # perfect but allows deltas to be generated reliably.
+ # ReferenceSet returns ResultSets, which doesn't really help the
+ # Snapshot object. We therefore list()ify the values; this isn't perfect
+ # but allows deltas to be generated reliably.
return shortlist(
value, longest_expected=100, hardlimit=HARD_LIMIT_FOR_SNAPSHOT
)
diff --git a/lib/lp/soyuz/model/publishing.py b/lib/lp/soyuz/model/publishing.py
index 5c32866..78749c0 100644
--- a/lib/lp/soyuz/model/publishing.py
+++ b/lib/lp/soyuz/model/publishing.py
@@ -34,8 +34,6 @@ from storm.info import ClassAlias
from storm.properties import DateTime, Int, Unicode
from storm.references import Reference
from storm.store import Store
-from storm.zope import IResultSet
-from storm.zope.interfaces import ISQLObjectResultSet
from zope.component import getUtility
from zope.interface import implementer
from zope.security.proxy import isinstance as zope_isinstance
@@ -1586,8 +1584,6 @@ class PublishingSet:
if len(bpphs) == 0:
return
else:
- if ISQLObjectResultSet.providedBy(bpphs):
- bpphs = IResultSet(bpphs)
if bpphs.is_empty():
return