Commit f577a9e1 authored by Julien Muchembled's avatar Julien Muchembled

Merge ZMySQLDDA product into ZMySQLDA

parent ccfbfcbc
...@@ -67,7 +67,7 @@ def findMessageListFromPythonInProduct(function_name_list): ...@@ -67,7 +67,7 @@ def findMessageListFromPythonInProduct(function_name_list):
'ERP5Form', 'ERP5OOo', 'ERP5Security', 'ERP5VCS', 'ERP5Form', 'ERP5OOo', 'ERP5Security', 'ERP5VCS',
'ERP5SyncML', 'ERP5Type', 'ERP5Wizard', 'ERP5Workflow', 'ERP5SyncML', 'ERP5Type', 'ERP5Wizard', 'ERP5Workflow',
'HBTreeFolder2', 'MailTemplates', 'TimerService', 'HBTreeFolder2', 'MailTemplates', 'TimerService',
'ZMySQLDA', 'ZMySQLDDA', 'ZSQLCatalog', 'ZMySQLDA', 'ZSQLCatalog',
) )
result = [] result = []
def findStaticMessage(file_path): def findStaticMessage(file_path):
......
...@@ -1890,11 +1890,12 @@ class ERP5Generator(PortalGenerator): ...@@ -1890,11 +1890,12 @@ class ERP5Generator(PortalGenerator):
if p.erp5_sql_deferred_connection_type == \ if p.erp5_sql_deferred_connection_type == \
'Z MySQL Deferred Database Connection': 'Z MySQL Deferred Database Connection':
if not p.hasObject('erp5_sql_deferred_connection'): if not p.hasObject('erp5_sql_deferred_connection'):
addSQLConnection = p.manage_addProduct['ZMySQLDDA'].\ addSQLConnection = p.manage_addProduct['ZMySQLDA'].\
manage_addZMySQLDeferredConnection manage_addZMySQLConnection
addSQLConnection('erp5_sql_deferred_connection', addSQLConnection('erp5_sql_deferred_connection',
'ERP5 SQL Server Deferred Connection', 'ERP5 SQL Server Deferred Connection',
p.erp5_sql_deferred_connection_string) p.erp5_sql_deferred_connection_string,
deferred=True)
elif p.erp5_sql_deferred_connection_type == 'Z Gadfly': elif p.erp5_sql_deferred_connection_type == 'Z Gadfly':
pass pass
......
...@@ -38,7 +38,6 @@ From ERP5 subversion repository: ...@@ -38,7 +38,6 @@ From ERP5 subversion repository:
* ERP5SyncML * ERP5SyncML
* ERP5Type * ERP5Type
* ZSQLCatalog * ZSQLCatalog
* ZMySQLDDA
Installation Installation
============ ============
......
...@@ -98,17 +98,24 @@ from App.special_dtml import HTMLFile ...@@ -98,17 +98,24 @@ from App.special_dtml import HTMLFile
from App.ImageFile import ImageFile from App.ImageFile import ImageFile
from DateTime import DateTime from DateTime import DateTime
from . import DABase from . import DABase
from .db import DB from .db import DB, DeferredDB
SHARED_DC_ZRDB_LOCATION = os.path.dirname(Shared.DC.ZRDB.__file__) SHARED_DC_ZRDB_LOCATION = os.path.dirname(Shared.DC.ZRDB.__file__)
manage_addZMySQLConnectionForm=HTMLFile('connectionAdd',globals()) manage_addZMySQLConnectionForm=HTMLFile('connectionAdd',globals())
def manage_addZMySQLConnection(self, id, title, def manage_addZMySQLConnection(self, id, title, connection_string,
connection_string, check=None, deferred=False, REQUEST=None):
check=None, REQUEST=None): """Add a MySQL connection to a folder.
"""Add a DB connection to a folder"""
connection = Connection(id, title, connection_string) Arguments:
REQUEST -- The current request
title -- The title of the ZMySQLDA Connection (string)
id -- The id of the ZMySQLDA Connection (string)
connection_string -- see connectionAdd.dtml
"""
cls = DeferredConnection if deferred else Connection
connection = cls(id, title, connection_string)
self._setObject(id, connection) self._setObject(id, connection)
if check: if check:
connection.connect(connection_string) connection.connect(connection_string)
...@@ -119,7 +126,8 @@ def manage_addZMySQLConnection(self, id, title, ...@@ -119,7 +126,8 @@ def manage_addZMySQLConnection(self, id, title,
database_connection_pool = defaultdict(WeakKeyDictionary) database_connection_pool = defaultdict(WeakKeyDictionary)
class Connection(DABase.Connection): class Connection(DABase.Connection):
" " """MySQL Connection Object
"""
database_type=database_type database_type=database_type
id='%s_database_connection' % database_type id='%s_database_connection' % database_type
meta_type=title='Z %s Database Connection' % database_type meta_type=title='Z %s Database Connection' % database_type
...@@ -164,6 +172,28 @@ class Connection(DABase.Connection): ...@@ -164,6 +172,28 @@ class Connection(DABase.Connection):
return connection.string_literal(v) return connection.string_literal(v)
class DeferredConnection(Connection):
"""
Experimental MySQL DA which implements
deferred SQL code execution to reduce locking issues
"""
meta_type=title='Z %s Deferred Database Connection' % database_type
def factory(self): return DeferredDB
# BBB: Allow loading of deferred connections that were created
# before the merge of ZMySQLDDA into ZMySQLDA.
import sys, imp
m = 'Products.ZMySQLDDA'
assert m not in sys.modules, "please remove obsolete ZMySQLDDA product"
sys.modules[m] = imp.new_module(m)
m += '.DA'
sys.modules[m] = m = imp.new_module(m)
m.DeferredConnection = DeferredConnection
del m
meta_types=( meta_types=(
{'name':'Z %s Database Connection' % database_type, {'name':'Z %s Database Connection' % database_type,
'action':'manage_addZ%sConnectionForm' % database_type, 'action':'manage_addZ%sConnectionForm' % database_type,
......
...@@ -100,7 +100,3 @@ def initialize(context): ...@@ -100,7 +100,3 @@ def initialize(context):
constructors=(DA.manage_addZMySQLConnectionForm, constructors=(DA.manage_addZMySQLConnectionForm,
DA.manage_addZMySQLConnection), DA.manage_addZMySQLConnection),
) )
context.registerHelp()
context.registerHelpTitle('ZMySQLDA')
...@@ -28,6 +28,12 @@ ...@@ -28,6 +28,12 @@
<input type="TEXT" name="connection_string" size="40"> <input type="TEXT" name="connection_string" size="40">
</td> </td>
</tr> </tr>
<tr>
<th align="LEFT" valign="TOP">Create Deferred Connection</th>
<td align="LEFT" valign="TOP">
<input name="deferred:int" type="CHECKBOX" value="1">
</td>
</tr>
<tr> <tr>
<th align="LEFT" valign="TOP">Connect immediately</th> <th align="LEFT" valign="TOP">Connect immediately</th>
<td align="LEFT" valign="TOP"> <td align="LEFT" valign="TOP">
......
...@@ -165,6 +165,7 @@ def ord_or_None(s): ...@@ -165,6 +165,7 @@ def ord_or_None(s):
return ord(s) return ord(s)
class DB(TM): class DB(TM):
"""This is the ZMySQLDA Database Connection Object."""
conv=conversions.copy() conv=conversions.copy()
conv[FIELD_TYPE.LONG] = int conv[FIELD_TYPE.LONG] = int
...@@ -253,6 +254,7 @@ class DB(TM): ...@@ -253,6 +254,7 @@ class DB(TM):
def tables(self, rdb=0, def tables(self, rdb=0,
_care=('TABLE', 'VIEW')): _care=('TABLE', 'VIEW')):
"""Returns a list of tables in the current database."""
r=[] r=[]
a=r.append a=r.append
result = self._query("SHOW TABLES") result = self._query("SHOW TABLES")
...@@ -263,6 +265,7 @@ class DB(TM): ...@@ -263,6 +265,7 @@ class DB(TM):
return r return r
def columns(self, table_name): def columns(self, table_name):
"""Returns a list of column descriptions for 'table_name'."""
try: try:
c = self._query('SHOW COLUMNS FROM %s' % table_name) c = self._query('SHOW COLUMNS FROM %s' % table_name)
except Exception: except Exception:
...@@ -360,6 +363,7 @@ class DB(TM): ...@@ -360,6 +363,7 @@ class DB(TM):
return self.db.store_result() return self.db.store_result()
def query(self, query_string, max_rows=1000): def query(self, query_string, max_rows=1000):
"""Execute 'query_string' and return at most 'max_rows'."""
self._use_TM and self._register() self._use_TM and self._register()
desc = None desc = None
# XXX deal with a typical mistake that the user appends # XXX deal with a typical mistake that the user appends
...@@ -394,6 +398,7 @@ class DB(TM): ...@@ -394,6 +398,7 @@ class DB(TM):
return self.db.string_literal(s) return self.db.string_literal(s)
def _begin(self, *ignored): def _begin(self, *ignored):
"""Begin a transaction (when TM is enabled)."""
try: try:
self._transaction_begun = True self._transaction_begun = True
# Ping the database to reconnect if connection was closed. # Ping the database to reconnect if connection was closed.
...@@ -408,6 +413,7 @@ class DB(TM): ...@@ -408,6 +413,7 @@ class DB(TM):
raise raise
def _finish(self, *ignored): def _finish(self, *ignored):
"""Commit a transaction (when TM is enabled)."""
if not self._transaction_begun: if not self._transaction_begun:
return return
self._transaction_begun = False self._transaction_begun = False
...@@ -417,6 +423,7 @@ class DB(TM): ...@@ -417,6 +423,7 @@ class DB(TM):
self._query("COMMIT") self._query("COMMIT")
def _abort(self, *ignored): def _abort(self, *ignored):
"""Rollback a transaction (when TM is enabled)."""
if not self._transaction_begun: if not self._transaction_begun:
return return
self._transaction_begun = False self._transaction_begun = False
...@@ -427,3 +434,44 @@ class DB(TM): ...@@ -427,3 +434,44 @@ class DB(TM):
else: else:
LOG('ZMySQLDA', ERROR, "aborting when non-transactional") LOG('ZMySQLDA', ERROR, "aborting when non-transactional")
class DeferredDB(DB):
"""
An experimental MySQL DA which implements deferred execution
of SQL code in order to reduce locks and provide better behaviour
with MyISAM non transactional tables
"""
def __init__(self, *args, **kw):
DB.__init__(self, *args, **kw)
assert self._use_TM
self._sql_string_list = []
def query(self,query_string, max_rows=1000):
self._register()
for qs in query_string.split('\0'):
qs = qs.strip()
if qs:
if qs[:6].upper() == "SELECT":
raise NotSupportedError(
"can not SELECT in deferred connections")
self._sql_string_list.append(qs)
return (),()
def _begin(self, *ignored):
# The Deferred DB instance is sometimes used for several
# transactions, so it is required to clear the sql_string_list
# each time a transaction starts
del self._sql_string_list[:]
def _finish(self, *ignored):
# BUG: It's wrong to execute queries here because tpc_finish must not
# fail. Consider moving them to commit, tpc_vote or in an
# after-commit hook.
if self._sql_string_list:
DB._begin(self)
for qs in self._sql_string_list:
self._query(qs)
del self._sql_string_list[:]
DB._finish(self)
_abort = _begin
def manage_addZMySQLConnection(self, id, title,
connection_string,
check=None, REQUEST=None):
"""Add a MySQL connection to a folder.
Arguments:
REQUEST -- The current request
title -- The title of the ZMySQLDA Connection (string)
id -- The id of the ZMySQLDA Connection (string)
connection_string -- The connection string is of the form:
'[*lock] [+/-][database][@host[:port]] [user [password [unix_socket]]]'
or typically:
'database user password'
to use a MySQL server on localhost via the standard UNIX
socket. Only specify host if the server is on a remote
system. You can use a non-standard port, if necessary. If the
UNIX socket is in a non-standard location, you can specify the
full path to it after the password. Hint: To use a
non-standard port on the local system, use 127.0.0.1 for the
host instead of localhost.
Either a database or a host or both must be specified.
A '-' in front of the database tells ZMySQLDA to not use
Zope's Transaction Manager, even if the server supports
transactions. A '+' in front of the database tells ZMySQLDA
that it must use transactions; an exception will be raised if
they are not supported by the server. If neither '-' or '+'
are present, then transactions will be enabled if the server
supports them. If you are using non-transaction safe tables
(TSTs) on a server that supports TSTs, use '-'. If you require
transactions, use '+'. If you aren't sure, don't use either.
*lock at the begining of the connection string means to
psuedo-transactional. When the transaction begins, it will
acquire a lock on the server named lock (i.e. MYLOCK). When
the transaction commits, the lock will be released. If the
transaction is aborted and restarted, which can happen due to
a ConflictError, you'll get an error in the logs, and
inconsistent data. In this respect, it's equivalent to
transactions turned off.
Transactions are highly recommended. Using a named lock in
conjunctions with transactions is probably pointless.
"""
class Connection:
"""MySQL Connection Object"""
__constructor__ = manage_addZMySQLConnection
def manage_addZMySQLConnection(self, id, title,
connection_string,
check=None, REQUEST=None):
"""Add a MySQL connection to a folder.
Arguments:
REQUEST -- The current request
title -- The title of the ZMySQLDA Connection (string)
id -- The id of the ZMySQLDA Connection (string)
connection_string -- The connection string is of the form:
'database[@host[:port]] [user [password [unix_socket]]]'
or typically:
'database user password'
to use a MySQL server on localhost via the standard UNIX socket.
Only specify host if the server is on a remote system. You can
use a non-standard port, if necessary. If the UNIX socket is in
a non-standard location, you can specify the full path to it
after the password.
"""
class Connection:
"""MySQL Connection Object"""
__constructor__ = manage_addZMySQLConnection
class DB:
"""This is the ZMySQLDA Database Connection Object."""
def __init__(self,connection):
"""
connection
blah blah
"""
def tables(self, rdb=0,
_care=('TABLE', 'VIEW')):
"""Returns a list of tables in the current database."""
def columns(self, table_name):
"""Returns a list of column descriptions for 'table_name'."""
def query(self,query_string, max_rows=1000):
"""Execute 'query_string' and return at most 'max_rows'."""
def _begin(self, *ignored):
"""Begin a transaction (when TM is enabled)."""
def _finish(self, *ignored):
"""Commit a transaction (when TM is enabled)."""
def _abort(self, *ignored):
"""Rollback a transaction (when TM is enabled)."""
...@@ -30,7 +30,6 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase ...@@ -30,7 +30,6 @@ from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import newSecurityManager
from _mysql_exceptions import OperationalError from _mysql_exceptions import OperationalError
from Products.ZMySQLDA.db import hosed_connection from Products.ZMySQLDA.db import hosed_connection
from thread import get_ident
from zLOG import LOG from zLOG import LOG
UNCONNECTED_STATE = 0 UNCONNECTED_STATE = 0
...@@ -97,22 +96,16 @@ class TestDeferredConnection(ERP5TypeTestCase): ...@@ -97,22 +96,16 @@ class TestDeferredConnection(ERP5TypeTestCase):
Revert monkeypatching done on db. Revert monkeypatching done on db.
""" """
connection.__class__._forceReconnection = connection.__class__.original_forceReconnection connection.__class__._forceReconnection = connection.__class__.original_forceReconnection
delattr(connection.__class__, 'original_forceReconnection') del connection.__class__.original_forceReconnection
mysql_class = connection.db.__class__ mysql_class = connection.db.__class__
mysql_class.query = mysql_class.original_query mysql_class.query = mysql_class.original_query
delattr(mysql_class, 'original_query') del mysql_class.original_query
def getDeferredConnection(self): def getDeferredConnection(self):
""" """
Return site's deferred connection object. Return site's deferred connection object.
""" """
deferred = self.getPortal().erp5_sql_deferred_connection return self.portal.erp5_sql_deferred_connection()
deferred_connection = getattr(deferred, '_v_database_connection', None)
if deferred_connection is None:
deferred.connect(deferred.connection_string)
deferred_connection = getattr(deferred, '_v_database_connection')
deferred_connection.tables() # Dummy access to force actual connection.
return deferred_connection._pool_get(get_ident())
def test_00_basicReplaceQuery(self): def test_00_basicReplaceQuery(self):
""" """
...@@ -151,7 +144,7 @@ class TestDeferredConnection(ERP5TypeTestCase): ...@@ -151,7 +144,7 @@ class TestDeferredConnection(ERP5TypeTestCase):
self.fail() self.fail()
finally: finally:
self.abort() self.abort()
delattr(connection, '_query') del connection._query
self.unmonkeypatchConnection(connection) self.unmonkeypatchConnection(connection)
def test_02_disconnectionRobustness(self): def test_02_disconnectionRobustness(self):
......
Z MySQL Deferred DA Releases
2.0.9
Initial Release
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
# Copyright (c) Nexedi SARL 2004. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
database_type='MySQL'
import os.path
from db import ThreadedDeferredDB
import Shared.DC.ZRDB
import DABase
from App.Dialogs import MessageDialog
from App.special_dtml import HTMLFile
from App.ImageFile import ImageFile
from ExtensionClass import Base
from DateTime import DateTime
from thread import allocate_lock
SHARED_DC_ZRDB_LOCATION = os.path.dirname(Shared.DC.ZRDB.__file__)
manage_addZMySQLDeferredConnectionForm=HTMLFile('deferredConnectionAdd',globals())
def manage_addZMySQLDeferredConnection(self, id, title,
connection_string,
check=None, REQUEST=None):
"""Add a DB connection to a folder"""
self._setObject(id, DeferredConnection(id, title, connection_string, check))
if REQUEST is not None: return self.manage_main(self,REQUEST)
# Connection Pool for connections to MySQL.
database_connection_pool_lock = allocate_lock()
database_connection_pool = {}
class DeferredConnection(DABase.Connection):
"""
Experimental MySQL DA which implements
deferred SQL code execution to reduce locking issues
"""
database_type=database_type
id='%s_database_connection' % database_type
meta_type=title='Z %s Deferred Database Connection' % database_type
icon='misc_/Z%sDDA/conn' % database_type
manage_properties=HTMLFile('connectionEdit', globals())
def factory(self): return ThreadedDeferredDB
def connect(self, s):
try:
database_connection_pool_lock.acquire()
self._v_connected = ''
pool_key = self.getPhysicalPath()
connection = database_connection_pool.get(pool_key)
if connection is not None and connection._connection == s:
self._v_database_connection = connection
else:
if connection is not None:
connection.closeConnection()
ThreadedDeferredDB = self.factory()
database_connection_pool[pool_key] = ThreadedDeferredDB(s)
self._v_database_connection = database_connection_pool[pool_key]
# XXX If date is used as such, it can be wrong because an existing
# connection may be reused. But this is suposedly only used as a
# marker to know if connection was successfull.
self._v_connected = DateTime()
finally:
database_connection_pool_lock.release()
return self
def sql_quote__(self, v, escapes={}):
return self._v_database_connection.string_literal(v)
classes=('DA.DeferredConnection')
meta_types=(
{'name':'Z %s Deferred Database Connection' % database_type,
'action':'manage_addZ%sDeferredConnectionForm' % database_type,
},
)
folder_methods={
'manage_addZMySQLDeferredConnection':
manage_addZMySQLDeferredConnection,
'manage_addZMySQLDeferredConnectionForm':
manage_addZMySQLDeferredConnectionForm,
}
__ac_permissions__=(
('Add Z MySQL Database Connections',
('manage_addZMySQLDeferredConnectionForm',
'manage_addZMySQLDeferredConnection')),
)
misc_={'conn': ImageFile(
os.path.join(SHARED_DC_ZRDB_LOCATION,'www','DBAdapterFolder_icon.gif'))}
for icon in ('table', 'view', 'stable', 'what',
'field', 'text','bin','int','float',
'date','time','datetime'):
misc_[icon]=ImageFile(os.path.join('icons','%s.gif') % icon, globals())
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
__doc__='''Database Connection
$Id: DABase.py,v 1.5 2001/08/17 02:17:38 adustman Exp $'''
__version__='$Revision: 1.5 $'[11:-2]
import Shared.DC.ZRDB.Connection, sys
from App.special_dtml import HTMLFile
from ExtensionClass import Base
import Acquisition
class Connection(Shared.DC.ZRDB.Connection.Connection):
_isAnSQLConnection=1
manage_options=Shared.DC.ZRDB.Connection.Connection.manage_options+(
{'label': 'Browse', 'action':'manage_browse'},
# {'label': 'Design', 'action':'manage_tables'},
)
manage_tables=HTMLFile('tables',globals())
manage_browse=HTMLFile('browse',globals())
info=None
def tpValues(self):
#if hasattr(self, '_v_tpValues'): return self._v_tpValues
r=[]
# self._v_tables=tables=TableBrowserCollection()
#tables=tables.__dict__
c=self._v_database_connection
try:
for d in c.tables(rdb=0):
try:
name=d['TABLE_NAME']
b=TableBrowser()
b.__name__=name
b._d=d
b._c=c
#b._columns=c.columns(name)
b.icon=table_icons.get(d['TABLE_TYPE'],'text')
r.append(b)
# tables[name]=b
except:
# print d['TABLE_NAME'], sys.exc_type, sys.exc_value
pass
finally: pass #print sys.exc_type, sys.exc_value
#self._v_tpValues=r
return r
def __getitem__(self, name):
if name=='tableNamed':
if not hasattr(self, '_v_tables'): self.tpValues()
return self._v_tables.__of__(self)
raise KeyError, name
def manage_wizard(self, tables):
" "
def manage_join(self, tables, select_cols, join_cols, REQUEST=None):
"""Create an SQL join"""
def manage_insert(self, table, cols, REQUEST=None):
"""Create an SQL insert"""
def manage_update(self, table, keys, cols, REQUEST=None):
"""Create an SQL update"""
class TableBrowserCollection(Acquisition.Implicit):
"Helper class for accessing tables via URLs"
class Browser(Base):
def __getattr__(self, name):
try: return self._d[name]
except KeyError: raise AttributeError, name
class values:
def len(self): return 1
def __getitem__(self, i):
try: return self._d[i]
except AttributeError: pass
self._d=self._f()
return self._d[i]
class TableBrowser(Browser, Acquisition.Implicit):
icon='what'
Description=check=''
info=HTMLFile('table_info',globals())
menu=HTMLFile('table_menu',globals())
def tpValues(self):
v=values()
v._f=self.tpValues_
return v
def tpValues_(self):
r=[]
tname=self.__name__
for d in self._c.columns(tname):
b=ColumnBrowser()
b._d=d
b.icon=d['Icon']
b.TABLE_NAME=tname
r.append(b)
return r
def tpId(self): return self._d['TABLE_NAME']
def tpURL(self): return "Table/%s" % self._d['TABLE_NAME']
def Name(self): return self._d['TABLE_NAME']
def Type(self): return self._d['TABLE_TYPE']
manage_designInput=HTMLFile('designInput',globals())
def manage_buildInput(self, id, source, default, REQUEST=None):
"Create a database method for an input form"
args=[]
values=[]
names=[]
columns=self._columns
for i in range(len(source)):
s=source[i]
if s=='Null': continue
c=columns[i]
d=default[i]
t=c['Type']
n=c['Name']
names.append(n)
if s=='Argument':
values.append("<!--#sql-value %s type=%s-->'" %
(n, vartype(t)))
a='%s%s' % (n, boboType(t))
if d: a="%s=%s" % (a,d)
args.append(a)
elif s=='Property':
values.append("<!--#sql-value %s type=%s-->'" %
(n, vartype(t)))
else:
if isStringType(t):
if find(d,"\'") >= 0: d=join(split(d,"\'"),"''")
values.append("'%s'" % d)
elif d:
values.append(str(d))
else:
raise ValueError, (
'no default was given for <em>%s</em>' % n)
class ColumnBrowser(Browser):
icon='field'
def check(self):
return ('\t<input type=checkbox name="%s.%s">' %
(self.TABLE_NAME, self._d['Name']))
def tpId(self): return self._d['Name']
def tpURL(self): return "Column/%s" % self._d['Name']
def Description(self): return " %s" % self._d['Description']
table_icons={
'TABLE': 'table',
'VIEW':'view',
'SYSTEM_TABLE': 'stable',
}
* MySQL-python-0.9.2 or newer
* Zope-2.3.0 or newer
* ZMySQLDA 2.0.9 or newer
Z MySQL Deferred DA
This is the Z MySQL database deferred adapter product for the
Z Object Publishing Environment. It is based on
ZMySQLDA and follows the same API and installation
procedure.
The main difference with ZMySQLDA is that the execution
of SQL expressions is deferred and executed during the Zope
commit time rather than immediately. This allows for example
to group INSERT and DELETE statements in a very short amount of
time, which reduces risks of lock. It also allows to use
MyISAM tables without raising useless exception messages related
to the non transactional nature of MyISAM.
** IMPORTANT **
SELECT expressions will not work in deferred mode
\ No newline at end of file
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
__doc__='''Generic Deferred Database Adapter Package Registration
$Id$'''
__version__='$Revision$'[11:-2]
import sys, string
import DA
methods=DA.folder_methods
misc_=DA.misc_
def initialize(context):
context.registerClass(
DA.DeferredConnection,
permission='Add Z MySQL Deferred Database Connections',
constructors=(DA.manage_addZMySQLDeferredConnectionForm,
DA.manage_addZMySQLDeferredConnection,)
)
context.registerHelp()
context.registerHelpTitle('ZMySQLDDA')
<html>
<head><title><!--#var title_or_id--> tables</title></head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
<!--#var manage_tabs-->
<!--#tree header=info-->
<IMG SRC="<!--#var BASEPATH1-->/misc_/ZMySQLDA/<!--#var icon-->"
ALT="<!--#var Type-->" BORDER="0">
<!--#var Name--><!--#var Description-->
<!--#/tree-->
</body>
</html>
<html>
<head><title>Add Z MySQL Deferred Database Connection</title></head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
<h2>Add Z MySQL Deferred Database Connection</h2>
<form action="manage_addZMySQLConnection" method="POST">
<table cellspacing="2">
<tr>
<th align="LEFT" valign="TOP">Id</th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="id" size="40"
value="MySQL_database_connection">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Title</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="40"
value="Z MySQL Database Connection">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP">
Enter a Database Connection String
<a href="#1"><sup>1</sup></a></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="connection_string" size="40">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP">Connect immediately</th>
<td align="LEFT" valign="TOP">
<input name="check" type="CHECKBOX" value="YES" CHECKED>
</td>
</tr>
<tr>
<td></td>
<td><br><input type="SUBMIT" value="Add"></td>
</tr>
</table>
</form>
<dt><a hname="1"><sup>1</sup></a> Connection Strings</dt>
<dd>
<p>
The connection string used for Z MySQL Database Connection
is of the form:
<pre>
[~] [*lock] [+/-][database][@host[:port]] [user [password [unix_socket]]]
</pre>
or typically:
<pre>
database user password
</pre>
to use a MySQL server on localhost via the standard UNIX socket.
Only specify host if the server is on a remote system. You can
use a non-standard port, if necessary. Hint: To use a non-standard
port on the local system, use 127.0.0.1 for the host instead of
localhost.
<p>
Either a database or a host or both must be specified.
<p>
If the UNIX socket is in a non-standard location, you can specify
the full path to it after the password.
<p>
A '-' in front of the database tells ZMySQLDA to not use Zope's
Transaction Manager, even if the server supports transactions. A
'+' in front of the database tells ZMySQLDA that it must use
transactions; an exception will be raised if they are not
supported by the server. If neither '-' or '+' are present, then
transactions will be enabled if the server supports them. If you
are using non-transaction safe tables (TSTs) on a server that
supports TSTs, use '-'. If you require transactions, use '+'. If
you aren't sure, don't use either.
<p>
A '~' at the begining of the connection string enables client-server
exchange compression (if supported by both client and server).
<p>
*<em>lock</em> means to
psuedo-transactional. When the transaction begins, it will acquire
a lock on the server named <em>lock</em> (i.e. MYLOCK). When the
transaction commits, the lock will be released. If the transaction
is aborted and restarted, which can happen due to a ConflictError,
you'll get an error in the logs, and inconsistent data. In this
respect, it's equivalent to transactions turned off.
<p>
Transactions are highly recommended. Using a named lock in
conjunctions with transactions is probably pointless.
</dd></dl>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html lang="en">
<head><title>Edit <dtml-var title_or_id></title></head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
<dtml-var manage_tabs>
<h2>Edit <dtml-var title_or_id></h2>
<form action="manage_edit" method="POST">
<table cellspacing="2">
<tr>
<th align="LEFT" valign="TOP">Id</th>
<td align="LEFT" valign="TOP"><dtml-var id></td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Title</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="40"
value="<dtml-var title html_quote>">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP">Database Connection String</th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="connection_string" size="40"
value="<dtml-var connection_string html_quote>">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP">Connect immediately</th>
<td align="LEFT" valign="TOP">
<input name="check" type="CHECKBOX" value="YES" CHECKED>
</td>
</tr>
<tr>
<td></td>
<td><br><input type="SUBMIT" value="Change"></td>
</tr>
</table>
</form>
</body>
</html>
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
# Copyright (c) Nexedi SARL 2004. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
import _mysql
import MySQLdb
from _mysql_exceptions import OperationalError, NotSupportedError
MySQLdb_version_required = (0,9,2)
_v = getattr(_mysql, 'version_info', (0,0,0))
if _v < MySQLdb_version_required:
raise NotSupportedError, \
"ZMySQLDDA requires at least MySQLdb %s, %s found" % \
(MySQLdb_version_required, _v)
from MySQLdb.converters import conversions
from MySQLdb.constants import FIELD_TYPE, CR, ER, CLIENT
from Shared.DC.ZRDB.TM import TM
from DateTime import DateTime
from ZODB.POSException import ConflictError
from string import strip, split, upper, rfind
from thread import get_ident, allocate_lock
hosed_connection = (
CR.SERVER_GONE_ERROR,
CR.SERVER_LOST
)
query_syntax_error = (
ER.BAD_FIELD_ERROR,
)
lock_error = (
ER.LOCK_WAIT_TIMEOUT,
ER.LOCK_DEADLOCK,
)
key_types = {
"PRI": "PRIMARY KEY",
"MUL": "INDEX",
"UNI": "UNIQUE",
}
field_icons = "bin", "date", "datetime", "float", "int", "text", "time"
icon_xlate = {
"varchar": "text", "char": "text",
"enum": "what", "set": "what",
"double": "float", "numeric": "float",
"blob": "bin", "mediumblob": "bin", "longblob": "bin",
"tinytext": "text", "mediumtext": "text",
"longtext": "text", "timestamp": "datetime",
"decimal": "float", "smallint": "int",
"mediumint": "int", "bigint": "int",
}
type_xlate = {
"double": "float", "numeric": "float",
"decimal": "float", "smallint": "int",
"mediumint": "int", "bigint": "int",
"int": "int", "float": "float",
"timestamp": "datetime", "datetime": "datetime",
"time": "datetime",
}
def _mysql_timestamp_converter(s):
if len(s) < 14:
s = s + "0"*(14-len(s))
parts = map(int, (s[:4],s[4:6],s[6:8],
s[8:10],s[10:12],s[12:14]))
return DateTime("%04d-%02d-%02d %02d:%02d:%02d" % tuple(parts))
def DateTime_or_None(s):
try: return DateTime(s)
except: return None
def int_or_long(s):
try: return int(s)
except: return long(s)
def ord_or_None(s):
if s is not None:
return ord(s)
class ThreadedDeferredDB:
"""
An experimental MySQL DA which implements deferred execution
of SQL code in order to reduce locks and provide better behaviour
with MyISAM non transactional tables
"""
conv=conversions.copy()
conv[FIELD_TYPE.LONG] = int_or_long
conv[FIELD_TYPE.DATETIME] = DateTime_or_None
conv[FIELD_TYPE.DATE] = DateTime_or_None
conv[FIELD_TYPE.DECIMAL] = float
conv[FIELD_TYPE.BIT] = ord_or_None
del conv[FIELD_TYPE.TIME]
def __init__(self,connection):
"""
Parse the connection string.
Initiate a trial connection with the database to check
transactionality once instead of once per DeferredDB instance.
"""
self._connection = connection
self._kw_args = self._parse_connection_string(connection)
self._db_pool = {}
self._db_lock = allocate_lock()
connection = MySQLdb.connect(**self._kw_args)
transactional = connection.server_capabilities & CLIENT.TRANSACTIONS
connection.close()
if self._try_transactions == '-':
transactional = 0
elif not transactional and self._try_transactions == '+':
raise NotSupportedError, "transactions not supported by this server"
self._use_TM = self._transactions = transactional
if self._mysql_lock:
self._use_TM = 1
def _parse_connection_string(self, connection):
kwargs = {'conv': self.conv}
items = split(connection)
self._use_TM = None
if not items: return kwargs
compress = items[0]
if compress == "~":
kwargs['compress'] = True
items = items[1:]
lockreq, items = items[0], items[1:]
if lockreq[0] == "*":
self._mysql_lock = lockreq[1:]
db_host, items = items[0], items[1:]
self._use_TM = 1
else:
self._mysql_lock = None
db_host = lockreq
if '@' in db_host:
db, host = split(db_host,'@',1)
kwargs['db'] = db
if host.startswith('['):
host, port = split(host[1:], ']', 1)
if port.startswith(':'):
kwargs['port'] = int(port[1:])
elif ':' in host:
host, port = split(host,':',1)
kwargs['port'] = int(port)
kwargs['host'] = host
else:
kwargs['db'] = db_host
if kwargs['db'] and kwargs['db'][0] in ('+', '-'):
self._try_transactions = kwargs['db'][0]
kwargs['db'] = kwargs['db'][1:]
else:
self._try_transactions = None
if not kwargs['db']:
del kwargs['db']
if not items: return kwargs
kwargs['user'], items = items[0], items[1:]
if not items: return kwargs
kwargs['passwd'], items = items[0], items[1:]
if not items: return kwargs
kwargs['unix_socket'], items = items[0], items[1:]
return kwargs
def _pool_set(self, key, value):
self._db_lock.acquire()
try:
self._db_pool[key] = value
finally:
self._db_lock.release()
def _pool_get(self, key):
self._db_lock.acquire()
try:
return self._db_pool.get(key)
finally:
self._db_lock.release()
def _pool_del(self, key):
self._db_lock.acquire()
try:
del self._db_pool[key]
finally:
self._db_lock.release()
def closeConnection(self):
ident = get_ident()
try:
self._pool_del(ident)
except KeyError:
pass
def _access_db(self, method_id, args, kw):
"""
Generic method to call pooled objects' methods.
When the current thread had never issued any call, create a DeferredDB
instance.
"""
ident = get_ident()
db = self._pool_get(ident)
if db is None:
db = DeferredDB(kw_args=self._kw_args, use_TM=self._use_TM,
mysql_lock=self._mysql_lock,
transactions=self._transactions)
self._pool_set(ident, db)
return getattr(db, method_id)(*args, **kw)
def tables(self, *args, **kw):
return self._access_db(method_id='tables', args=args, kw=kw)
def columns(self, *args, **kw):
return self._access_db(method_id='columns', args=args, kw=kw)
def query(self, *args, **kw):
return self._access_db(method_id='query', args=args, kw=kw)
def string_literal(self, *args, **kw):
return self._access_db(method_id='string_literal', args=args, kw=kw)
class DeferredDB(TM):
"""
An experimental MySQL DA which implements deferred execution
of SQL code in order to reduce locks and provide better behaviour
with MyISAM non transactional tables
"""
defs={
FIELD_TYPE.CHAR: "i", FIELD_TYPE.DATE: "d",
FIELD_TYPE.DATETIME: "d", FIELD_TYPE.DECIMAL: "n",
FIELD_TYPE.DOUBLE: "n", FIELD_TYPE.FLOAT: "n", FIELD_TYPE.INT24: "i",
FIELD_TYPE.LONG: "i", FIELD_TYPE.LONGLONG: "l",
FIELD_TYPE.SHORT: "i", FIELD_TYPE.TIMESTAMP: "d",
FIELD_TYPE.TINY: "i", FIELD_TYPE.YEAR: "i",
}
_p_oid=_p_changed=_registered=None
def __init__(self, kw_args, use_TM, mysql_lock, transactions):
self._kw_args = kw_args
self._mysql_lock = mysql_lock
self._use_TM = use_TM
self._transactions = transactions
self._forceReconnection()
self._sql_string_list = []
def __del__(self):
self.db.close()
def _forceReconnection(self):
db = MySQLdb.connect(**self._kw_args)
self.db = db
def tables(self, rdb=0,
_care=('TABLE', 'VIEW')):
r=[]
a=r.append
result = self._query("SHOW TABLES")
row = result.fetch_row(1)
while row:
a({'TABLE_NAME': row[0][0], 'TABLE_TYPE': 'TABLE'})
row = result.fetch_row(1)
return r
def columns(self, table_name):
from string import join
try:
c = self._query('SHOW COLUMNS FROM %s' % table_name)
except:
return ()
r=[]
for Field, Type, Null, Key, Default, Extra in c.fetch_row(0):
info = {}
field_default = Default and "DEFAULT %s"%Default or ''
if Default: info['Default'] = Default
if '(' in Type:
end = rfind(Type,')')
short_type, size = split(Type[:end],'(',1)
if short_type not in ('set','enum'):
if ',' in size:
info['Scale'], info['Precision'] = \
map(int, split(size,',',1))
else:
info['Scale'] = int(size)
else:
short_type = Type
if short_type in field_icons:
info['Icon'] = short_type
else:
info['Icon'] = icon_xlate.get(short_type, "what")
info['Name'] = Field
info['Type'] = type_xlate.get(short_type,'string')
info['Extra'] = Extra,
info['Description'] = join([Type, field_default, Extra or '',
key_types.get(Key, Key or ''),
Null != 'YES' and 'NOT NULL' or '']),
info['Nullable'] = (Null == 'YES') and 1 or 0
if Key:
info['Index'] = 1
if Key == 'PRI':
info['PrimaryKey'] = 1
info['Unique'] = 1
elif Key == 'UNI':
info['Unique'] = 1
r.append(info)
return r
def _query(self, query, force_reconnect=False):
"""
Send a to MySQL server.
It reconnects automaticaly if needed and the following conditions are
met:
- It has not just tried to reconnect (ie, this function will not
attemp to connect twice per call).
- This conection is not transactionnal and has set not MySQL locks,
because they are bound to the connection. This check can be
overridden by passing force_reconnect with True value.
"""
try:
self.db.query(query)
except OperationalError, m:
if m[0] in query_syntax_error:
raise OperationalError(m[0], '%s: %s' % (m[1], query))
if m[0] in lock_error:
raise ConflictError('%s: %s: %s' % (m[0], m[1], query))
if ((not force_reconnect) and \
(self._mysql_lock or self._transactions)) or \
m[0] not in hosed_connection:
raise
# Hm. maybe the db is hosed. Let's restart it.
self._forceReconnection()
self.db.query(query)
return self.db.store_result()
def query(self,query_string, max_rows=1000):
self._use_TM and self._register()
for qs in filter(None, map(strip,split(query_string, '\0'))):
qtype = upper(split(qs, None, 1)[0])
if qtype == "SELECT":
raise NotSupportedError, "can not SELECT in deferred connections"
self._sql_string_list.append(qs)
return (),()
def string_literal(self, s):
return self.db.string_literal(s)
def _begin(self, *ignored):
# The Deferred DB instance is sometimes used for several
# transactions, so it is required to clear the sql_string_list
# each time a transaction starts
self._sql_string_list = []
self._transaction_begun = True
def _finish(self, *ignored):
if not self._transaction_begun:
return
self._transaction_begun = False
# Ping the database to reconnect if connection was lost.
self._query("SELECT 1", force_reconnect=True)
if self._transactions:
self._query("BEGIN")
if self._mysql_lock:
self._query("SELECT GET_LOCK('%s',0)" % self._mysql_lock)
for qs in self._sql_string_list:
self._query(qs)
if self._mysql_lock:
self._query("SELECT RELEASE_LOCK('%s')" % self._mysql_lock)
if self._transactions:
self._query("COMMIT")
def _abort(self, *ignored):
self._transaction_begun = False
<html>
<head><title>Add Z MySQL Deferred Database Connection</title></head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
<h2>Add Z MySQL Deferred Database Connection</h2>
<form action="manage_addZMySQLDeferredConnection" method="POST">
<table cellspacing="2">
<tr>
<th align="LEFT" valign="TOP">Id</th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="id" size="40"
value="MySQL_database_connection">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP"><em>Title</em></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="title" size="40"
value="Z MySQL Database Connection">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP">
Enter a Database Connection String
<a href="#1"><sup>1</sup></a></th>
<td align="LEFT" valign="TOP">
<input type="TEXT" name="connection_string" size="40">
</td>
</tr>
<tr>
<th align="LEFT" valign="TOP">Connect immediately</th>
<td align="LEFT" valign="TOP">
<input name="check" type="CHECKBOX" value="YES" CHECKED>
</td>
</tr>
<tr>
<td></td>
<td><br><input type="SUBMIT" value="Add"></td>
</tr>
</table>
</form>
<dt><a hname="1"><sup>1</sup></a> Connection Strings</dt>
<dd>
<p>
The connection string used for Z MySQL Database Connection
is of the form:
<pre>
[*lock] [+/-][database][@host[:port]] [user [password [unix_socket]]]
</pre>
or typically:
<pre>
database user password
</pre>
to use a MySQL server on localhost via the standard UNIX socket.
Only specify host if the server is on a remote system. You can
use a non-standard port, if necessary. Hint: To use a non-standard
port on the local system, use 127.0.0.1 for the host instead of
localhost.
<p>
Either a database or a host or both must be specified.
<p>
If the UNIX socket is in a non-standard location, you can specify
the full path to it after the password.
<p>
A '-' in front of the database tells ZMySQLDA to not use Zope's
Transaction Manager, even if the server supports transactions. A
'+' in front of the database tells ZMySQLDA that it must use
transactions; an exception will be raised if they are not
supported by the server. If neither '-' or '+' are present, then
transactions will be enabled if the server supports them. If you
are using non-transaction safe tables (TSTs) on a server that
supports TSTs, use '-'. If you require transactions, use '+'. If
you aren't sure, don't use either.
<p>
*<em>lock</em> at the begining of the connection string means to
psuedo-transactional. When the transaction begins, it will acquire
a lock on the server named <em>lock</em> (i.e. MYLOCK). When the
transaction commits, the lock will be released. If the transaction
is aborted and restarted, which can happen due to a ConflictError,
you'll get an error in the logs, and inconsistent data. In this
respect, it's equivalent to transactions turned off.
<p>
Transactions are highly recommended. Using a named lock in
conjunctions with transactions is probably pointless.
</dd></dl>
</body>
</html>
def manage_addZMySQLDeferredConnection(self, id, title,
connection_string,
check=None, REQUEST=None):
"""Add a MySQL deferred connection to a folder.
Arguments:
REQUEST -- The current request
title -- The title of the ZMySQLDA Connection (string)
id -- The id of the ZMySQLDA Connection (string)
connection_string -- The connection string is of the form:
'[*lock] [+/-][database][@host[:port]] [user [password [unix_socket]]]'
or typically:
'database user password'
to use a MySQL server on localhost via the standard UNIX
socket. Only specify host if the server is on a remote
system. You can use a non-standard port, if necessary. If the
UNIX socket is in a non-standard location, you can specify the
full path to it after the password. Hint: To use a
non-standard port on the local system, use 127.0.0.1 for the
host instead of localhost.
Either a database or a host or both must be specified.
A '-' in front of the database tells ZMySQLDA to not use
Zope's Transaction Manager, even if the server supports
transactions. A '+' in front of the database tells ZMySQLDA
that it must use transactions; an exception will be raised if
they are not supported by the server. If neither '-' or '+'
are present, then transactions will be enabled if the server
supports them. If you are using non-transaction safe tables
(TSTs) on a server that supports TSTs, use '-'. If you require
transactions, use '+'. If you aren't sure, don't use either.
*lock at the begining of the connection string means to
psuedo-transactional. When the transaction begins, it will
acquire a lock on the server named lock (i.e. MYLOCK). When
the transaction commits, the lock will be released. If the
transaction is aborted and restarted, which can happen due to
a ConflictError, you'll get an error in the logs, and
inconsistent data. In this respect, it's equivalent to
transactions turned off.
Transactions are highly recommended. Using a named lock in
conjunctions with transactions is probably pointless.
"""
class DeferredConnection:
"""MySQL Deferred Connection Object"""
__constructor__ = manage_addZMySQLDeferredConnection
def manage_addZMySQLDeferredConnection(self, id, title,
connection_string,
check=None, REQUEST=None):
"""Add a MySQL deferred connection to a folder.
Arguments:
REQUEST -- The current request
title -- The title of the ZMySQLDA Connection (string)
id -- The id of the ZMySQLDA Connection (string)
connection_string -- The connection string is of the form:
'database[@host[:port]] [user [password [unix_socket]]]'
or typically:
'database user password'
to use a MySQL server on localhost via the standard UNIX socket.
Only specify host if the server is on a remote system. You can
use a non-standard port, if necessary. If the UNIX socket is in
a non-standard location, you can specify the full path to it
after the password.
"""
class DeferredConnection:
"""MySQL Deferred Connection Object"""
__constructor__ = manage_addZMySQLDeferredConnection
class DB:
"""This is the ZMySQLDDA Deferred Database Connection Object."""
def __init__(self,connection):
"""
connection
blah blah
"""
def tables(self, rdb=0,
_care=('TABLE', 'VIEW')):
"""Returns a list of tables in the current database."""
def columns(self, table_name):
"""Returns a list of column descriptions for 'table_name'."""
def query(self,query_string, max_rows=1000):
"""Execute 'query_string' and return at most 'max_rows'."""
def _begin(self, *ignored):
"""Begin a transaction (when TM is enabled)."""
def _finish(self, *ignored):
"""Commit a transaction (when TM is enabled)."""
def _abort(self, *ignored):
"""Rollback a transaction (when TM is enabled)."""
<dtml-var standard_html_header>
<dtml-var TABLE_TYPE><dtml-if TABLE_OWNER>
owned by <dtml-var TABLE_OWNER></dtml-if>
<dtml-if REMARKS><br><dtml-var REMARKS></dtml-if>
<dtml-var standard_html_footer>
...@@ -11,8 +11,7 @@ class _ERP5(ERP5TypeTestSuite): ...@@ -11,8 +11,7 @@ class _ERP5(ERP5TypeTestSuite):
'ERP5VCS', 'ERP5Wizard', 'Formulator', 'ERP5Workflow', 'ERP5VCS', 'ERP5Wizard', 'Formulator', 'ERP5Workflow',
'ERP5Configurator','HBTreeFolder2', 'MailTemplates', 'ERP5Configurator','HBTreeFolder2', 'MailTemplates',
'PortalTransforms', 'TimerService', 'ZLDAPConnection', 'PortalTransforms', 'TimerService', 'ZLDAPConnection',
'ZLDAPMethods', 'ZMySQLDA', 'ZMySQLDDA', 'ZSQLCatalog', 'ZLDAPMethods', 'ZMySQLDA', 'ZSQLCatalog', 'Zelenium')
'Zelenium')
def enableProducts(self): def enableProducts(self):
product_set = set(self.enabled_product_list) product_set = set(self.enabled_product_list)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment