Merge remote-tracking branch 'upstream/master' into pg8000
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "gluon/packages/dal"]
|
||||||
|
path = gluon/packages/dal
|
||||||
|
url = https://github.com/web2py/pydal.git
|
||||||
+4
-42
@@ -4,59 +4,21 @@ python:
|
|||||||
- '2.6'
|
- '2.6'
|
||||||
- '2.7'
|
- '2.7'
|
||||||
- 'pypy'
|
- 'pypy'
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- pip install -e .
|
- pip install -e .
|
||||||
env:
|
|
||||||
- DB=sqlite:memory
|
|
||||||
- DB=mysql://root:@localhost/test_w2p
|
|
||||||
- DB=postgres://postgres:@localhost/test_w2p
|
|
||||||
- DB=google:datastore
|
|
||||||
# - DB=google:datastore+ndb
|
|
||||||
- DB=mongodb://mongodb:mongodb@localhost/test_w2p
|
|
||||||
- DB=imap://imap:imap@localhost:993
|
|
||||||
before_script:
|
before_script:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install unittest2; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install coverage; fi;
|
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install python-coveralls; fi
|
|
||||||
- if [[ $DB == postgres* ]]; then pip install psycopg2; fi;
|
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi
|
|
||||||
- if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi
|
|
||||||
- if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi
|
|
||||||
- if [[ $DB == postgres* ]]; then psql -c 'create extension postgis;' -U postgres -d test_w2p; fi
|
|
||||||
|
|
||||||
|
|
||||||
# Install last sdk for app engine (update only whenever a new release is available)
|
|
||||||
- if [[ $DB == google* ]]; then wget http://googleappengine.googlecode.com/files/google_appengine_1.8.9.zip -nv; fi
|
|
||||||
- if [[ $DB == google* ]]; then unzip -q google_appengine_1.8.9.zip; fi
|
|
||||||
- if [[ $DB == google* ]]; then mv -f ./google_appengine/google ./google; fi
|
|
||||||
|
|
||||||
- if [[ $DB == mongodb* ]]; then pip install pymongo; fi
|
|
||||||
- if [[ $DB == mongodb* ]]; then mongo test_w2p --eval 'db.addUser("mongodb", "mongodb");'; fi
|
|
||||||
|
|
||||||
#Temporal solution to travis issue #155
|
#Temporal solution to travis issue #155
|
||||||
- sudo chmod 777 /dev/shm
|
- sudo chmod 777 /dev/shm
|
||||||
- sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
|
- sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
|
||||||
matrix:
|
|
||||||
exclude:
|
|
||||||
- python: 'pypy'
|
|
||||||
env: DB=postgres://postgres:@localhost/test_w2p
|
|
||||||
- python: 'pypy'
|
|
||||||
env: DB=mysql://root:@localhost/test_w2p
|
|
||||||
- python: 'pypy'
|
|
||||||
env: DB=google:datastore
|
|
||||||
- python: '2.6'
|
|
||||||
env: DB=google:datastore
|
|
||||||
# - python: '2.6'
|
|
||||||
# env: DB=google:datastore+ndb
|
|
||||||
|
|
||||||
|
|
||||||
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
|
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: true
|
email: true
|
||||||
|
|
||||||
services: mongodb
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
## 2.9.12
|
||||||
|
|
||||||
|
- Tornado HTTPS support, thanks Diego
|
||||||
|
- Modular DAL, thanks Giovanni
|
||||||
|
- Added coverage support, thanks Niphlod
|
||||||
|
- More tests, thanks Niphlod and Paolo Valleri
|
||||||
|
- Added support for show_if in readonly sqlform, thanks Paolo
|
||||||
|
- Improved scheduler, thanks Niphlod
|
||||||
|
- Email timeout support
|
||||||
|
- Made web2py's custom_import work with circular imports, thanks Jack Kuan
|
||||||
|
- Added Portuguese, Catalan, and Burmese translations
|
||||||
|
- Allow map_hyphen to work for application names, thanks Tim Nyborg
|
||||||
|
- New module appconfig.py, thanks Niphlod
|
||||||
|
- Added geospatial support to Teradata adaptor, thanks Andrew Willimott
|
||||||
|
- Many bug fixes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 2.9.6 - 2.9.10
|
## 2.9.6 - 2.9.10
|
||||||
|
|
||||||
- fixed support of GAE + SQL
|
- fixed support of GAE + SQL
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ update:
|
|||||||
echo "remember that pymysql was tweaked"
|
echo "remember that pymysql was tweaked"
|
||||||
src:
|
src:
|
||||||
### Use semantic versioning
|
### Use semantic versioning
|
||||||
echo 'Version 2.10.0-beta+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
echo 'Version 2.9.12-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
|
||||||
### rm -f all junk files
|
### rm -f all junk files
|
||||||
make clean
|
make clean
|
||||||
### clean up baisc apps
|
### clean up baisc apps
|
||||||
@@ -61,7 +61,7 @@ src:
|
|||||||
### build web2py_src.zip
|
### build web2py_src.zip
|
||||||
echo '' > NEWINSTALL
|
echo '' > NEWINSTALL
|
||||||
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
|
||||||
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/dal/* web2py/gluon/contrib/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
|
||||||
|
|
||||||
mdp:
|
mdp:
|
||||||
make src
|
make src
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 2.10.0-beta+timestamp.2014.10.16.15.58.50
|
Version 2.9.12-stable+timestamp.2015.01.17.00.07.04
|
||||||
|
|||||||
@@ -1278,7 +1278,7 @@ def create_file():
|
|||||||
path = abspath(request.vars.location)
|
path = abspath(request.vars.location)
|
||||||
else:
|
else:
|
||||||
if request.vars.dir:
|
if request.vars.dir:
|
||||||
request.vars.location += request.vars.dir + '/'
|
request.vars.location += request.vars.dir + '/'
|
||||||
app = get_app(name=request.vars.location.split('/')[0])
|
app = get_app(name=request.vars.location.split('/')[0])
|
||||||
path = apath(request.vars.location, r=request)
|
path = apath(request.vars.location, r=request)
|
||||||
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
filename = re.sub('[^\w./-]+', '_', request.vars.filename)
|
||||||
@@ -1434,36 +1434,36 @@ def create_file():
|
|||||||
""" % URL('edit', args=[app,request.vars.dir,filename])
|
""" % URL('edit', args=[app,request.vars.dir,filename])
|
||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
redirect(request.vars.sender + anchor)
|
redirect(request.vars.sender + anchor)
|
||||||
|
|
||||||
|
|
||||||
def listfiles(app, dir, regexp='.*\.py$'):
|
def listfiles(app, dir, regexp='.*\.py$'):
|
||||||
files = sorted(
|
files = sorted(
|
||||||
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
|
||||||
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def editfile(path,file,vars={}, app = None):
|
def editfile(path,file,vars={}, app = None):
|
||||||
args=(path,file) if 'app' in vars else (app,path,file)
|
args=(path,file) if 'app' in vars else (app,path,file)
|
||||||
url = URL('edit', args=args, vars=vars)
|
url = URL('edit', args=args, vars=vars)
|
||||||
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
|
return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;')
|
||||||
|
|
||||||
def files_menu():
|
def files_menu():
|
||||||
app = request.vars.app or 'welcome'
|
app = request.vars.app or 'welcome'
|
||||||
dirs=[{'name':'models', 'reg':'.*\.py$'},
|
dirs=[{'name':'models', 'reg':'.*\.py$'},
|
||||||
{'name':'controllers', 'reg':'.*\.py$'},
|
{'name':'controllers', 'reg':'.*\.py$'},
|
||||||
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
|
||||||
{'name':'modules', 'reg':'.*\.py$'},
|
{'name':'modules', 'reg':'.*\.py$'},
|
||||||
{'name':'static', 'reg': '[^\.#].*'},
|
{'name':'static', 'reg': '[^\.#].*'},
|
||||||
{'name':'private', 'reg':'.*\.py$'}]
|
{'name':'private', 'reg':'.*\.py$'}]
|
||||||
result_files = []
|
result_files = []
|
||||||
for dir in dirs:
|
for dir in dirs:
|
||||||
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
|
result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"),
|
||||||
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
|
LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.','__')), app), _style="overflow:hidden", _id=dir['name']+"__"+f.replace('.','__'))
|
||||||
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
|
for f in listfiles(app, dir['name'], regexp=dir['reg'])],
|
||||||
_class="nav nav-list small-font"),
|
_class="nav nav-list small-font"),
|
||||||
_id=dir['name'] + '_files', _style="display: none;")))
|
_id=dir['name'] + '_files', _style="display: none;")))
|
||||||
return dict(result_files = result_files)
|
return dict(result_files = result_files)
|
||||||
|
|
||||||
def upload_file():
|
def upload_file():
|
||||||
""" File uploading handler """
|
""" File uploading handler """
|
||||||
@@ -1941,4 +1941,3 @@ def install_plugin():
|
|||||||
T('unable to install plugin "%s"', filename)
|
T('unable to install plugin "%s"', filename)
|
||||||
redirect(URL(f="plugins", args=[app,]))
|
redirect(URL(f="plugins", args=[app,]))
|
||||||
return dict(form=form, app=app, plugin=plugin, source=source)
|
return dict(form=form, app=app, plugin=plugin, source=source)
|
||||||
|
|
||||||
|
|||||||
@@ -325,7 +325,7 @@
|
|||||||
'unable to uninstall "%s"': 'impossibile disinstallare "%s"',
|
'unable to uninstall "%s"': 'impossibile disinstallare "%s"',
|
||||||
'unable to upgrade because "%s"': 'impossibile aggiornare perché "%s"',
|
'unable to upgrade because "%s"': 'impossibile aggiornare perché "%s"',
|
||||||
'uncheck all': 'smarca tutti',
|
'uncheck all': 'smarca tutti',
|
||||||
'Uninstall': 'disinstalla',
|
'Uninstall': 'Disinstalla',
|
||||||
'update': 'aggiorna',
|
'update': 'aggiorna',
|
||||||
'update all languages': 'aggiorna tutti i linguaggi',
|
'update all languages': 'aggiorna tutti i linguaggi',
|
||||||
'Update:': 'Aggiorna:',
|
'Update:': 'Aggiorna:',
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
'Versioning': 'Versioning',
|
'Versioning': 'Versioning',
|
||||||
'View': 'Vista',
|
'View': 'Vista',
|
||||||
'view': 'vista',
|
'view': 'vista',
|
||||||
'Views': 'viste',
|
'Views': 'Viste',
|
||||||
'views': 'viste',
|
'views': 'viste',
|
||||||
'Web Framework': 'Web Framework',
|
'Web Framework': 'Web Framework',
|
||||||
'web2py is up to date': 'web2py è aggiornato',
|
'web2py is up to date': 'web2py è aggiornato',
|
||||||
|
|||||||
@@ -47,5 +47,3 @@ if 'adminLanguage' in request.cookies and not (request.cookies['adminLanguage']
|
|||||||
#set static_version
|
#set static_version
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
response.static_version = global_settings.web2py_version.split('-')[0]
|
response.static_version = global_settings.web2py_version.split('-')[0]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,3 @@ else:
|
|||||||
URL(_a, 'default', f='logout')))
|
URL(_a, 'default', f='logout')))
|
||||||
response.menu.append((T('Debug'), False,
|
response.menu.append((T('Debug'), False,
|
||||||
URL(_a, 'debug', 'interact')))
|
URL(_a, 'debug', 'interact')))
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,3 @@ def stateWidget(field, value, data={'on-label':'Enabled', 'off-label':'Disabled'
|
|||||||
jQuery(".make-bootstrap-switch input[name='%s']").parent().bootstrapSwitch();
|
jQuery(".make-bootstrap-switch input[name='%s']").parent().bootstrapSwitch();
|
||||||
""" % fieldName)
|
""" % fieldName)
|
||||||
return DIV(div, script)
|
return DIV(div, script)
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><a class="btn btn-180 btn-success" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a></td>
|
<td><a class="btn btn-180 btn-success" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a></td>
|
||||||
<td><a class="btn btn-180" href="{{=URL('changelog')}}">Change Log</a></td>
|
<td><a class="btn btn-180" href="{{=URL('changelog')}}">Change Log</a></td>
|
||||||
<td><a class="btn btn-180" href="http://code.google.com/p/web2py/issues/list">Report a Bug</a></td>
|
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/issues">Report a Bug</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</center>
|
</center>
|
||||||
|
|||||||
@@ -0,0 +1,492 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'!langcode!': 'ca',
|
||||||
|
'!langname!': 'Català',
|
||||||
|
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"actualizi" és una expressió opcional com "camp1=\'nou_valor\'". No es poden actualitzar o eliminar resultats de un JOIN',
|
||||||
|
'%(nrows)s records found': '%(nrows)s registres trobats',
|
||||||
|
'%s %%{position}': '%s %%{posició}',
|
||||||
|
'%s %%{row} deleted': '%s %%{fila} %%{eliminada}',
|
||||||
|
'%s %%{row} updated': '%s %%{fila} %%{actualitzada}',
|
||||||
|
'%s selected': '%s %%{seleccionat}',
|
||||||
|
'%Y-%m-%d': '%d/%m/%Y',
|
||||||
|
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
|
||||||
|
'(something like "it-it")': '(similar a "això-això")',
|
||||||
|
'@markmin\x01An error occured, please [[reload %s]] the page': 'Hi ha hagut un error, si us plau [[recarregui %s]] la pàgina',
|
||||||
|
'@markmin\x01Number of entries: **%s**': "Nombre d'entrades: **%s**",
|
||||||
|
'A new version of web2py is available': 'Hi ha una nova versió de wep2py disponible',
|
||||||
|
'A new version of web2py is available: %s': 'Hi ha una nova versió de wep2py disponible: %s',
|
||||||
|
'About': 'Sobre',
|
||||||
|
'about': 'sobre',
|
||||||
|
'About application': "Sobre l'aplicació",
|
||||||
|
'Access Control': "Control d'Accés",
|
||||||
|
'Add': 'Afegir',
|
||||||
|
'Add Record': 'Afegeix registre',
|
||||||
|
'additional code for your application': '`codi addicional per a la seva aplicació',
|
||||||
|
'admin disabled because no admin password': 'admin inhabilitat per falta de contrasenya',
|
||||||
|
'admin disabled because not supported on google app engine': 'admin inhabilitat, no és suportat en GAE',
|
||||||
|
'admin disabled because unable to access password file': 'admin inhabilitat, impossible accedir al fitxer con la contrasenya',
|
||||||
|
'Admin is disabled because insecure channel': 'Admin inhabilitat, el canal no és segur',
|
||||||
|
'Admin is disabled because unsecure channel': 'Admin inhabilitat, el canal no és segur',
|
||||||
|
'Administrative interface': 'Interfície administrativa',
|
||||||
|
'Administrative Interface': 'Interfície Administrativa',
|
||||||
|
'administrative interface': 'interfície administrativa',
|
||||||
|
'Administrator Password:': 'Contrasenya del Administrador:',
|
||||||
|
'Ajax Recipes': 'Receptes AJAX',
|
||||||
|
'An error occured, please %s the page': 'Hi ha hagut un error, per favor %s la pàgina',
|
||||||
|
'And': 'I',
|
||||||
|
'and rename it (required):': 'i renombri-la (requerit):',
|
||||||
|
'and rename it:': " i renombri'l:",
|
||||||
|
'appadmin': 'appadmin',
|
||||||
|
'appadmin is disabled because insecure channel': 'admin inhabilitat, el canal no és segur',
|
||||||
|
'application "%s" uninstalled': 'aplicació "%s" desinstal·lada',
|
||||||
|
'application compiled': 'aplicació compilada',
|
||||||
|
'application is compiled and cannot be designed': 'la aplicació està compilada i no pot ser modificada',
|
||||||
|
'Apply changes': 'Aplicar canvis',
|
||||||
|
'Appointment': 'Nomenament',
|
||||||
|
'Are you sure you want to delete file "%s"?': 'Està segur que vol eliminar el arxiu "%s"?',
|
||||||
|
'Are you sure you want to delete this object?': 'Està segur que vol esborrar aquest objecte?',
|
||||||
|
'Are you sure you want to uninstall application "%s"': '¿Està segur que vol desinstalar la aplicació "%s"',
|
||||||
|
'Are you sure you want to uninstall application "%s"?': '¿Està segur que vol desinstalar la aplicació "%s"?',
|
||||||
|
'at': 'a',
|
||||||
|
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENCIÓ: Inici de sessió requereix una connexió segura (HTTPS) o localhost.',
|
||||||
|
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENCION: NO EJECUTE VARIAS PRUEBAS SIMULTANEAMENTE, NO SON THREAD SAFE.',
|
||||||
|
'ATTENTION: you cannot edit the running application!': 'ATENCIO: no pot modificar la aplicació que està ejecutant-se!',
|
||||||
|
'Authentication': 'Autenticació',
|
||||||
|
'Authentication failed at client DB!': '¡La autenticació ha fallat en la BDD client!',
|
||||||
|
'Authentication failed at main DB!': '¡La autenticació ha fallat en la BDD principal!',
|
||||||
|
'Available Databases and Tables': 'Bases de dades i taules disponibles',
|
||||||
|
'Back': 'Endarrera',
|
||||||
|
'Buy this book': 'Compra aquest lllibre',
|
||||||
|
'Cache': 'Caché',
|
||||||
|
'cache': 'caché',
|
||||||
|
'Cache Cleared': 'Caché Netejada',
|
||||||
|
'Cache Keys': 'Claus de la Caché',
|
||||||
|
'cache, errors and sessions cleaned': 'caché, errors i sessions eliminats',
|
||||||
|
'Cannot be empty': 'No pot estar buit',
|
||||||
|
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'No se pot compilar: hi ha errors en la seva aplicació. Depuri, corregeixi errors i torni a intentar-ho.',
|
||||||
|
'cannot upload file "%(filename)s"': 'no és possible pujar fitxer "%(filename)s"',
|
||||||
|
'Change Password': 'Canviï la Contrasenya',
|
||||||
|
'Change password': 'Canviï la contrasenya',
|
||||||
|
'change password': 'canviï la contrasenya',
|
||||||
|
'Changelog': 'Changelog',
|
||||||
|
'check all': 'marcar tots',
|
||||||
|
'Check to delete': 'Marqui per a eliminar',
|
||||||
|
'choose one': 'escolliu un',
|
||||||
|
'clean': 'neteja',
|
||||||
|
'Clear': 'Netejar',
|
||||||
|
'Clear CACHE?': 'Netejar Memòrica Cau?',
|
||||||
|
'Clear DISK': 'Netejar DISC',
|
||||||
|
'Clear RAM': 'Netejar RAM',
|
||||||
|
'Click on the link %(link)s to reset your password': "Cliqui en l'enllaç %(link)s per a reiniciar la seva contrasenya",
|
||||||
|
'click to check for upgrades': 'feu clic per buscar actualitzacions',
|
||||||
|
'client': 'cliente',
|
||||||
|
'Client IP': 'IP del Client',
|
||||||
|
'Close': 'Tancar',
|
||||||
|
'Comma-separated export including columns not shown; fields from other tables are exported as raw values for faster export': 'Comma-separated export including columns not shown; fields from other tables are exported as raw values for faster export',
|
||||||
|
'Comma-separated export of visible columns. Fields from other tables are exported as they appear on-screen but this may be slow for many rows': 'Comma-separated export of visible columns. Fields from other tables are exported as they appear on-screen but this may be slow for many rows',
|
||||||
|
'Community': 'Comunitat',
|
||||||
|
'compile': 'compilar',
|
||||||
|
'compiled application removed': 'aplicació compilada eliminada',
|
||||||
|
'Components and Plugins': 'Components i Plugins',
|
||||||
|
'contains': 'conté',
|
||||||
|
'Controller': 'Controlador',
|
||||||
|
'Controllers': 'Controladors',
|
||||||
|
'controllers': 'controladors',
|
||||||
|
'Copyright': 'Copyright',
|
||||||
|
'Correo electrónico invàlid': 'Correu electrònic invàlid',
|
||||||
|
'create file with filename:': 'crear el fitxer amb el nom:',
|
||||||
|
'Create new application': 'Crear una nova aplicació',
|
||||||
|
'create new application:': 'crear una nova aplicació:',
|
||||||
|
'Create New Page': 'Crear Pàgina Nova',
|
||||||
|
'Create Page from Slug': 'Create Page from Slug',
|
||||||
|
'Created By': 'Creat Per',
|
||||||
|
'Created On': 'Creat a',
|
||||||
|
'CSV': 'CSV',
|
||||||
|
'CSV (hidden cols)': 'CSV (columnas ocultes)',
|
||||||
|
'Current request': 'Sol·licitud en curs',
|
||||||
|
'Current response': 'Resposta en curs',
|
||||||
|
'Current session': 'Sessió en curs',
|
||||||
|
'currently saved or': 'actualment guardat o',
|
||||||
|
'customize me!': "¡Adapta'm!",
|
||||||
|
'data uploaded': 'dades pujades',
|
||||||
|
'Database': 'Base de dades',
|
||||||
|
'Database %s select': 'selecció a base de dades %s',
|
||||||
|
'database administration': 'administració de base de dades',
|
||||||
|
'Database Administration (appadmin)': 'Administració de Base de Dades (appadmin)',
|
||||||
|
'Date and Time': 'Data i Hora',
|
||||||
|
'DB': 'BDD',
|
||||||
|
'db': 'bdd',
|
||||||
|
'DB Model': 'Model BDD',
|
||||||
|
'defines tables': 'defineix taules',
|
||||||
|
'Delete': 'Eliminar',
|
||||||
|
'delete': 'eliminar',
|
||||||
|
'delete all checked': 'eliminar marcats',
|
||||||
|
'Delete:': 'Eliminar:',
|
||||||
|
'Demo': 'Demostració',
|
||||||
|
'Deploy on Google App Engine': 'Desplegament a Google App Engine',
|
||||||
|
'Deployment Recipes': 'Receptes de desplegament',
|
||||||
|
'Description': 'Descripció',
|
||||||
|
'design': 'diseny',
|
||||||
|
'DESIGN': 'DISENY',
|
||||||
|
'Design for': 'Diseny per a',
|
||||||
|
'detecting': 'detectant',
|
||||||
|
'DISK': 'DISC',
|
||||||
|
'Disk Cache Keys': 'Claus de Caché en Disc',
|
||||||
|
'Disk Cleared': 'Disc netejat',
|
||||||
|
'Documentation': 'Documentació',
|
||||||
|
"Don't know what to do?": 'No sap què fer?',
|
||||||
|
'done!': '¡fet!',
|
||||||
|
'Download': 'Descàrregues',
|
||||||
|
'E-mail': 'Correu electrònic',
|
||||||
|
'edit': 'editar',
|
||||||
|
'EDIT': 'EDITAR',
|
||||||
|
'Edit': 'Editar',
|
||||||
|
'Edit application': 'Editar aplicació',
|
||||||
|
'edit controller': 'editar controlador',
|
||||||
|
'Edit current record': 'Editar el registre actual',
|
||||||
|
'Edit Menu': 'Editar Menu',
|
||||||
|
'Edit Page': 'Editar Pàgina',
|
||||||
|
'Edit Page Media': 'Edit Page Media',
|
||||||
|
'Edit Profile': 'Editar Perfil',
|
||||||
|
'edit profile': 'editar perfil',
|
||||||
|
'Edit This App': 'Editi aquesta App',
|
||||||
|
'Editing file': 'Editant fitxer',
|
||||||
|
'Editing file "%s"': 'Editant fitxer "%s"',
|
||||||
|
'El fitxer ha de ser PDF': 'El fitxer ha de ser PDF',
|
||||||
|
'El fitxer ha de ser PDF o XML': 'El fitxer ha de ser PDF o XML',
|
||||||
|
'Email': 'Email',
|
||||||
|
'Email and SMS': 'Correu electrònic i SMS',
|
||||||
|
'Email sent': 'Correu electrònic enviat',
|
||||||
|
'End of impersonation': 'Fi de suplantació',
|
||||||
|
'enter a number between %(min)g and %(max)g': 'introdueixi un número entre %(min)g i %(max)g',
|
||||||
|
'Enter a valid email address': 'Entri una adreça email vàlida',
|
||||||
|
'enter a value': 'entri un valor',
|
||||||
|
'Enter a value': 'Entri un valor',
|
||||||
|
'Enter an integer between %(min)g and %(max)g': 'Entri un numero enter entre %(min)g i %(max)g',
|
||||||
|
'enter an integer between %(min)g and %(max)g': 'entri numero enter entre %(min)g i %(max)g',
|
||||||
|
'enter date and time as %(format)s': 'entri data i hora com %(format)s',
|
||||||
|
'Enter from %(min)g to %(max)g characters': 'Entri des de %(min)g a %(max)g caràcters',
|
||||||
|
'Enter valid filename': 'Entri nom de fitxer vàlid',
|
||||||
|
'Error logs for "%(app)s"': 'Bitàcora de errors a "%(app)s"',
|
||||||
|
'errors': 'errors',
|
||||||
|
'Errors': 'Errors',
|
||||||
|
'Errors in form, please check it out.': 'Hi ha errors en el formulari, per favor comprovi-ho.',
|
||||||
|
'export as csv file': 'exportar com fitxer CSV',
|
||||||
|
'Export:': 'Exportar:',
|
||||||
|
'exposes': 'exposa',
|
||||||
|
'extends': 'extén',
|
||||||
|
'failed to reload module': 'la recàrrega del mòdul ha fallat',
|
||||||
|
'FAQ': 'FAQ',
|
||||||
|
'file': 'fitxer',
|
||||||
|
'file "%(filename)s" created': 'fitxer "%(filename)s" creat',
|
||||||
|
'file "%(filename)s" deleted': 'fitxer "%(filename)s" eliminat',
|
||||||
|
'file "%(filename)s" uploaded': 'fitxer "%(filename)s" pujat',
|
||||||
|
'file "%(filename)s" was not deleted': 'fitxer "%(filename)s" no fou eliminat',
|
||||||
|
'file "%s" of %s restored': 'fitxer "%s" de %s restaurat',
|
||||||
|
'file ## download': 'file ',
|
||||||
|
'file changed on disk': 'fitxer modificat en el disco',
|
||||||
|
'file does not exist': 'fitxer no existeix',
|
||||||
|
'file saved on %(time)s': 'fitxer guardat a %(time)s',
|
||||||
|
'file saved on %s': 'fitxer guardat a %s',
|
||||||
|
'First name': 'Nom',
|
||||||
|
'Forgot username?': 'Ha oblidat el nom de usuari?',
|
||||||
|
'Forms and Validators': 'Formularis i validadors',
|
||||||
|
'Free Applications': 'Aplicacions Lliures',
|
||||||
|
'Functions with no doctests will result in [passed] tests.': 'Funcions sense doctests equivalen a pruebas [aceptades].',
|
||||||
|
'Group %(group_id)s created': 'Grupo %(group_id)s creat',
|
||||||
|
'Group ID': 'ID de Grup',
|
||||||
|
'Group uniquely assigned to user %(id)s': 'Grup assignat únicament al usuari %(id)s',
|
||||||
|
'Groups': 'Grups',
|
||||||
|
'Hello': 'Hola',
|
||||||
|
'Hello World': 'Hola Món',
|
||||||
|
'help': 'ajuda',
|
||||||
|
'Home': 'Inici',
|
||||||
|
'Hosted by': 'Hosted by',
|
||||||
|
'How did you get here?': 'Com has arribat aquí?',
|
||||||
|
'HTML': 'HTML',
|
||||||
|
'HTML export of visible columns': 'HTML export de columnes visibles',
|
||||||
|
'htmledit': 'htmledit',
|
||||||
|
'Impersonate': 'Suplantar',
|
||||||
|
'import': 'importar',
|
||||||
|
'Import/Export': 'Importar/Exportar',
|
||||||
|
'in': 'a',
|
||||||
|
'includes': 'inclou',
|
||||||
|
'Index': 'Índex',
|
||||||
|
'insert new': 'inserti nou',
|
||||||
|
'insert new %s': 'inserti nou %s',
|
||||||
|
'Installed applications': 'Aplicacions instalades',
|
||||||
|
'Insufficient privileges': 'Privilegis insuficients',
|
||||||
|
'internal error': 'error intern',
|
||||||
|
'Internal State': 'Estat Intern',
|
||||||
|
'Introduction': 'Introducció',
|
||||||
|
'Invalid action': 'Acció invàlida',
|
||||||
|
'Invalid email': 'Correo electrónico invàlid',
|
||||||
|
'invalid expression': 'expressió invàlida',
|
||||||
|
'Invalid login': 'Inici de sessió invàlida',
|
||||||
|
'invalid password': 'contrasenya invàlida',
|
||||||
|
'Invalid Query': 'Consulta invàlida',
|
||||||
|
'invalid request': 'sol·licitud invàlida',
|
||||||
|
'Invalid reset password': 'Reinici de contrasenya invàlid',
|
||||||
|
'invalid ticket': 'tiquet invàlid',
|
||||||
|
'Is Active': 'Està Actiu',
|
||||||
|
'Key': 'Clau',
|
||||||
|
'language file "%(filename)s" created/updated': 'fitxer de llenguatge "%(filename)s" creat/actualitzat',
|
||||||
|
'Language files (static strings) updated': 'Fitxers de llenguatge (cadenes estàtiques) actualitzats',
|
||||||
|
'languages': 'llenguatges',
|
||||||
|
'Languages': 'Llenguatges',
|
||||||
|
'languages updated': 'llenguatges actualitzats',
|
||||||
|
'Last name': 'Cognom',
|
||||||
|
'Last saved on:': 'Guardat a:',
|
||||||
|
'Layout': 'Diseny de pàgina',
|
||||||
|
'Layout Plugins': 'Plugins de disseny',
|
||||||
|
'Layouts': 'Dissenys de pàgines',
|
||||||
|
'License for': 'Llicència per a',
|
||||||
|
'Live Chat': 'Xat en viu',
|
||||||
|
'loading...': 'carregant...',
|
||||||
|
'Log In': 'Log In',
|
||||||
|
'Log Out': 'Log Out',
|
||||||
|
'Logged in': 'Sessió iniciada',
|
||||||
|
'Logged out': 'Sessió finalitzada',
|
||||||
|
'Login': 'Inici de sessió',
|
||||||
|
'login': 'inici de sessió',
|
||||||
|
'Login disabled by administrator': 'Inici de sessió inhabilitat pel administrador',
|
||||||
|
'Login to the Administrative Interface': 'Inici de sessió per a la Interfície Administrativa',
|
||||||
|
'logout': 'fi de sessió',
|
||||||
|
'Logout': 'Fi de sessió',
|
||||||
|
'Lost Password': 'Contrasenya perdida',
|
||||||
|
'Lost password?': 'Ha oblidat la contrasenya?',
|
||||||
|
'lost password?': '¿ha oblidat la contrasenya?',
|
||||||
|
'Main Menu': 'Menú principal',
|
||||||
|
'Manage %(action)s': 'Manage %(action)s',
|
||||||
|
'Manage Access Control': 'Manage Access Control',
|
||||||
|
'Manage Cache': 'Gestionar la Caché',
|
||||||
|
'Menu Model': 'Model "menu"',
|
||||||
|
'merge': 'combinar',
|
||||||
|
'Models': 'Models',
|
||||||
|
'models': 'models',
|
||||||
|
'Modified By': 'Modificat Per',
|
||||||
|
'Modified On': 'Modificat A',
|
||||||
|
'Modules': 'Mòduls',
|
||||||
|
'modules': 'mòduls',
|
||||||
|
'must be YYYY-MM-DD HH:MM:SS!': '¡debe ser DD/MM/YYYY HH:MM:SS!',
|
||||||
|
'must be YYYY-MM-DD!': '¡debe ser DD/MM/YYYY!',
|
||||||
|
'My Sites': 'Els Meus Llocs',
|
||||||
|
'Name': 'Nombre',
|
||||||
|
'New': 'Nuevo',
|
||||||
|
'New %(entity)s': 'Nou %(entity)s',
|
||||||
|
'new application "%s" created': 'nova aplicació "%s" creada',
|
||||||
|
'New password': 'Contrasenya nova',
|
||||||
|
'New Record': 'Registre nou',
|
||||||
|
'new record inserted': 'nou registre insertat',
|
||||||
|
'New Search': 'Cerca nova',
|
||||||
|
'next %s rows': 'següents %s files',
|
||||||
|
'next 100 rows': '100 files següents',
|
||||||
|
'NO': 'NO',
|
||||||
|
'No databases in this application': 'No hi ha bases de dades en esta aplicació',
|
||||||
|
'No records found': "No s'han trobat registres",
|
||||||
|
'Not authorized': 'No autoritzat',
|
||||||
|
'not in': 'no a',
|
||||||
|
'Object or table name': 'Nom del objecte o taula',
|
||||||
|
'Old password': 'Contrasenya anterior',
|
||||||
|
'Online examples': 'Ejemples en línia',
|
||||||
|
'Or': 'O',
|
||||||
|
'or import from csv file': 'o importar desde fitxer CSV',
|
||||||
|
'or provide application url:': 'o proveeix URL de la aplicació:',
|
||||||
|
'Origin': 'Origen',
|
||||||
|
'Original/Translation': 'Original/Traducció',
|
||||||
|
'Other Plugins': 'Altres Plugins',
|
||||||
|
'Other Recipes': 'Altres Receptes',
|
||||||
|
'Overview': 'Resum',
|
||||||
|
'pack all': 'empaquetar tot',
|
||||||
|
'pack compiled': 'empaquetar compilats',
|
||||||
|
'Password': 'Contrasenya',
|
||||||
|
'Password changed': 'Contrasenya cambiada',
|
||||||
|
"Password fields don't match": 'Els camps de contrasenya no coincideixen',
|
||||||
|
'Password reset': 'Reinici de contrasenya',
|
||||||
|
'Peeking at file': 'Visualitzant fitxer',
|
||||||
|
'Permission': 'Permís',
|
||||||
|
'Permissions': 'Permisos',
|
||||||
|
'Phone': 'Telèfon',
|
||||||
|
'please input your password again': 'si us plau, entri un altre cop la seva contrasenya',
|
||||||
|
'Plugins': 'Plugins',
|
||||||
|
'Powered by': 'Aquest lloc utilitza',
|
||||||
|
'Preface': 'Prefaci',
|
||||||
|
'Presentar Factures': 'Presentar Factures',
|
||||||
|
'Presentar factures': 'Presentar factures',
|
||||||
|
'previous %s rows': '%s files prèvies',
|
||||||
|
'previous 100 rows': '100 files anteriors',
|
||||||
|
'Profile': 'Perfil',
|
||||||
|
'Profile updated': 'Perfil actualitzat',
|
||||||
|
'pygraphviz library not found': 'pygraphviz library not found',
|
||||||
|
'Python': 'Python',
|
||||||
|
'Query Not Supported: %s': 'Consulta No Suportada: %s',
|
||||||
|
'Query:': 'Consulta:',
|
||||||
|
'Quick Examples': 'Exemple Ràpids',
|
||||||
|
'RAM': 'RAM',
|
||||||
|
'RAM Cache Keys': 'Claus de la Caché en RAM',
|
||||||
|
'Ram Cleared': 'Ram Netjeda',
|
||||||
|
'Recipes': 'Receptes',
|
||||||
|
'Record': 'Registre',
|
||||||
|
'Record %(id)s created': 'Registre %(id)s creat',
|
||||||
|
'Record Created': 'Registre Creat',
|
||||||
|
'record does not exist': 'el registre no existe',
|
||||||
|
'Record ID': 'ID de Registre',
|
||||||
|
'Record id': 'Id de registre',
|
||||||
|
'Ref APB': 'Ref APB',
|
||||||
|
'register': "registri's",
|
||||||
|
'Register': "Registri's",
|
||||||
|
'Registration identifier': 'Identificador de Registre',
|
||||||
|
'Registration key': 'Clau de registre',
|
||||||
|
'Registration successful': 'Registre amb èxit',
|
||||||
|
'reload': 'recarregar',
|
||||||
|
'Remember me (for 30 days)': "Recordi'm (durant 30 dies)",
|
||||||
|
'remove compiled': 'eliminar compilades',
|
||||||
|
'Request reset password': 'Sol·licitud de restabliment de contrasenya',
|
||||||
|
'Reset password': 'Reiniciar contrasenya',
|
||||||
|
'Reset Password key': 'Restaurar Clau de la Contrasenya',
|
||||||
|
'Resolve Conflict file': 'Resolgui el Conflicte de fitxer',
|
||||||
|
'restore': 'restaurar',
|
||||||
|
'Retrieve username': 'Recuperar nom de usuari',
|
||||||
|
'revert': 'revertir',
|
||||||
|
'Role': 'Rol',
|
||||||
|
'Roles': 'Rols',
|
||||||
|
'Rows in Table': 'Files a la taula',
|
||||||
|
'Rows selected': 'Files seleccionades',
|
||||||
|
'save': 'guardar',
|
||||||
|
'Save model as...': 'Save model as...',
|
||||||
|
'Saved file hash:': 'Hash del fitxer guardat:',
|
||||||
|
'Search': 'Buscar',
|
||||||
|
'Search Pages': 'Search Pages',
|
||||||
|
'Semantic': 'Semàntica',
|
||||||
|
'Services': 'Serveis',
|
||||||
|
'session expired': 'sessió expirada',
|
||||||
|
'shell': 'terminal',
|
||||||
|
'Sign Up': 'Sign Up',
|
||||||
|
'site': 'lloc',
|
||||||
|
'Size of cache:': 'Mida de la Caché:',
|
||||||
|
'Slug': 'Slug',
|
||||||
|
'some files could not be removed': 'algunos archivos no pudieron ser removidos',
|
||||||
|
'Spreadsheet-optimised export of tab-separated content including hidden columns. May be slow': 'Spreadsheet-optimised export of tab-separated content including hidden columns. May be slow',
|
||||||
|
'Spreadsheet-optimised export of tab-separated content, visible columns only. May be slow.': 'Spreadsheet-optimised export of tab-separated content, visible columns only. May be slow.',
|
||||||
|
'start': 'inici',
|
||||||
|
'Start building a new search': 'Start building a new search',
|
||||||
|
'starts with': 'comença per',
|
||||||
|
'state': 'estat',
|
||||||
|
'static': 'estàtics',
|
||||||
|
'Static files': 'Fitxers estàtics',
|
||||||
|
'Statistics': 'Estadístiques',
|
||||||
|
'Stylesheet': "Fulla d'estil",
|
||||||
|
'Submit': 'Enviar',
|
||||||
|
'submit': 'enviar',
|
||||||
|
'Success!': 'Correcte!',
|
||||||
|
'Support': 'Suport',
|
||||||
|
'Sure you want to delete this object?': '¿Està segur que vol eliminar aquest objecte?',
|
||||||
|
'Table': 'taula',
|
||||||
|
'Table name': 'Nom de la taula',
|
||||||
|
'test': 'provar',
|
||||||
|
'Testing application': 'Provant aplicació',
|
||||||
|
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "consulta" és una condición com "db.tabla1.campo1==\'valor\'". Algo com "db.tabla1.campo1==db.tabla2.campo2" resulta en un JOIN SQL.',
|
||||||
|
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lògica de la aplicació, cada ruta URL es mapeja en una funció exposada en el controlador',
|
||||||
|
'The Core': 'El Nucli',
|
||||||
|
'the data representation, define database tables and sets': 'la representació de dades, defineix taules i conjunts de base de dades',
|
||||||
|
'The output of the file is a dictionary that was rendered by the view %s': 'El resultat de aquesta funció és un diccionari que és desplegat per la vista %s',
|
||||||
|
'the presentations layer, views are also known as templates': 'la capa de presentació, les vistes també són anomenades plantilles',
|
||||||
|
'The Views': 'Les Vistes',
|
||||||
|
'There are no controllers': 'No hi ha controladors',
|
||||||
|
'There are no models': 'No hi ha models',
|
||||||
|
'There are no modules': 'No hi ha mòduls',
|
||||||
|
'There are no static files': 'No hi ha fitxers estàtics',
|
||||||
|
'There are no translators, only default language is supported': 'No hi ha traductors, només el llenguatge per defecte és suportat',
|
||||||
|
'There are no views': 'No hi ha vistes',
|
||||||
|
'these files are served without processing, your images go here': 'aquests fitxers són servits sense processar, les seves imatges van aquí',
|
||||||
|
'This App': 'Aquesta Aplicació',
|
||||||
|
'This email already has an account': 'Aquest correu electrònic ja té un compte',
|
||||||
|
'This is a copy of the scaffolding application': 'Aquesta és una còpia de la aplicació de bastiment',
|
||||||
|
'This is the %(filename)s template': 'Aquesta és la plantilla %(filename)s',
|
||||||
|
'Ticket': 'Tiquet',
|
||||||
|
'Time in Cache (h:m:s)': 'Temps en Caché (h:m:s)',
|
||||||
|
'Timestamp': 'Marca de temps',
|
||||||
|
'Title': 'Títol',
|
||||||
|
'to previous version.': 'a la versió prèvia.',
|
||||||
|
'To emulate a breakpoint programatically, write:': 'Emular un punto de ruptura programàticament, escribir:',
|
||||||
|
'to use the debugger!': 'usar el depurador!',
|
||||||
|
'toggle breakpoint': 'alternar punt de ruptura',
|
||||||
|
'Toggle comment': 'Alternar comentari',
|
||||||
|
'Toggle Fullscreen': 'Alternar pantalla completa',
|
||||||
|
'too short': 'massa curt',
|
||||||
|
'Traceback': 'Traceback',
|
||||||
|
'translation strings for the application': 'cadenes de caracters de traducció per a la aplicació',
|
||||||
|
'try': 'intenti',
|
||||||
|
'try something like': 'intenti algo com',
|
||||||
|
'TSV (Excel compatible)': 'TSV (compatible Excel)',
|
||||||
|
'TSV (Excel compatible, hidden cols)': 'TSV (compatible Excel, columnes ocultes)',
|
||||||
|
'TSV (Spreadsheets)': 'TSV (Fulls de càlcul)',
|
||||||
|
'TSV (Spreadsheets, hidden cols)': 'TSV (Fulls de càlcul, columnes amagades)',
|
||||||
|
'Twitter': 'Twitter',
|
||||||
|
'Unable to check for upgrades': 'No és possible verificar la existencia de actualitzacions',
|
||||||
|
'unable to create application "%s"': 'no és possible crear la aplicació "%s"',
|
||||||
|
'unable to delete file "%(filename)s"': 'no és possible eliminar el fitxer "%(filename)s"',
|
||||||
|
'Unable to download': 'No és possible la descàrrega',
|
||||||
|
'Unable to download app': 'No és possible descarregar la aplicació',
|
||||||
|
'unable to parse csv file': 'no és possible analitzar el fitxer CSV',
|
||||||
|
'unable to uninstall "%s"': 'no és possible instalar "%s"',
|
||||||
|
'uncheck all': 'desmarcar tots',
|
||||||
|
'uninstall': 'desinstalar',
|
||||||
|
'unknown': 'desconocido',
|
||||||
|
'update': 'actualitzar',
|
||||||
|
'update all languages': 'actualitzar tots els llenguatges',
|
||||||
|
'Update:': 'Actualizi:',
|
||||||
|
'upload application:': 'pujar aplicació:',
|
||||||
|
'Upload existing application': 'Puji aquesta aplicació',
|
||||||
|
'upload file:': 'puji fitxer:',
|
||||||
|
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, i ~(...) para NOT, para crear consultas més complexes.',
|
||||||
|
'User': 'Usuari',
|
||||||
|
'User %(id)s is impersonating %(other_id)s': 'El usuari %(id)s està suplantant %(other_id)s',
|
||||||
|
'User %(id)s Logged-in': 'El usuari %(id)s inicià la sessió',
|
||||||
|
'User %(id)s Logged-out': 'El usuari %(id)s finalitzà la sessió',
|
||||||
|
'User %(id)s Password changed': 'Contrasenya del usuari %(id)s canviada',
|
||||||
|
'User %(id)s Password reset': 'Contrasenya del usuari %(id)s reiniciada',
|
||||||
|
'User %(id)s Profile updated': 'Actualitzat el perfil del usuari %(id)s',
|
||||||
|
'User %(id)s Registered': 'Usuari %(id)s Registrat',
|
||||||
|
'User %(id)s Username retrieved': 'Se ha recuperat el nom de usuari del usuari %(id)s',
|
||||||
|
'User %(username)s Logged-in': 'El usuari %(username)s inicià la sessió',
|
||||||
|
"User '%(username)s' Logged-in": "El usuari '%(username)s' inicià la sessió",
|
||||||
|
"User '%(username)s' Logged-out": "El usuari '%(username)s' finalitzà la sessió",
|
||||||
|
'User Id': 'Id de Usuari',
|
||||||
|
'User ID': 'ID de Usuari',
|
||||||
|
'User Logged-out': 'El usuari finalitzà la sessió',
|
||||||
|
'Username': 'Nom de usuari',
|
||||||
|
'Username retrieve': 'Recuperar nom de usuari',
|
||||||
|
'Users': 'Usuaris',
|
||||||
|
'Value already in database or empty': 'El valor ya existeix en la base de dades o està buit',
|
||||||
|
'value already in database or empty': 'el valor ya existeix en la base de dades o està buit',
|
||||||
|
'value not allowed': 'valor no permès',
|
||||||
|
'Value not in database': 'El valor no està a la base de dades',
|
||||||
|
'value not in database': 'el valor no està a la base de dades',
|
||||||
|
'Verify Password': 'Verificar Contrasenya',
|
||||||
|
'Version': 'Versió',
|
||||||
|
'versioning': 'versions',
|
||||||
|
'Videos': 'Videos',
|
||||||
|
'View': 'Vista',
|
||||||
|
'view': 'vista',
|
||||||
|
'View %(entity)s': 'Veure %(entity)s',
|
||||||
|
'View Page': 'View Page',
|
||||||
|
'Views': 'Vistes',
|
||||||
|
'views': 'vistes',
|
||||||
|
'web2py is up to date': 'web2py està actualitzat',
|
||||||
|
'web2py Recent Tweets': 'Tweets Recents de web2py',
|
||||||
|
'Welcome': 'Benvingut',
|
||||||
|
'Welcome %s': 'Benvingut %s',
|
||||||
|
'Welcome to web2py': 'Benvingut a web2py',
|
||||||
|
'Welcome to web2py!': '¡Benvingut a web2py!',
|
||||||
|
'Which called the function %s located in the file %s': 'La qual va cridar la funció %s localitzada en el fitxer %s',
|
||||||
|
'Wiki Page': 'Wiki Page',
|
||||||
|
'Working...': 'Treballant ...',
|
||||||
|
'XML': 'XML',
|
||||||
|
'XML export of columns shown': 'XML export of columns shown',
|
||||||
|
'YES': 'SÍ',
|
||||||
|
'You are successfully running web2py': 'Vostè està executant web2py amb èxit',
|
||||||
|
'You can modify this application and adapt it to your needs': 'Vostè pot modificar aquesta aplicació i adaptar-la a les seves necessitats',
|
||||||
|
'You visited the url %s': 'Vostè va visitar la url %s',
|
||||||
|
'Your username is: %(username)s': 'El seu nom de usuari és: %(username)s',
|
||||||
|
}
|
||||||
@@ -12,6 +12,23 @@ Web2Py framework modules
|
|||||||
|
|
||||||
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
|
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_LIST_OF_EMAILS', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
|
||||||
|
|
||||||
|
#: add pydal to sys.modules
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
try:
|
||||||
|
sys.path.append(os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)), "packages", "dal"))
|
||||||
|
import pydal
|
||||||
|
sys.modules['pydal'] = pydal
|
||||||
|
except:
|
||||||
|
raise RuntimeError(
|
||||||
|
"web2py depends on pydal, which apparently you have not installed.\n" +
|
||||||
|
"Probably you cloned the repository using git without '--recursive'.\n" +
|
||||||
|
"To fix this, please run (from inside your web2py folder):\n\n" +
|
||||||
|
" git submodule update\n\n" +
|
||||||
|
"You can also download a complete copy from from http://www.web2py.com."
|
||||||
|
)
|
||||||
|
|
||||||
from globals import current
|
from globals import current
|
||||||
from html import *
|
from html import *
|
||||||
from validators import *
|
from validators import *
|
||||||
|
|||||||
+1
-1
@@ -121,7 +121,7 @@ def app_cleanup(app, request):
|
|||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
for f in os.listdir(path):
|
for f in os.listdir(path):
|
||||||
try:
|
try:
|
||||||
if f[:1] != '.': os.unlink(os.path.join(path, f))
|
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
|
||||||
except IOError:
|
except IOError:
|
||||||
r = False
|
r = False
|
||||||
return r
|
return r
|
||||||
|
|||||||
+48
-6
@@ -246,11 +246,11 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
"""
|
"""
|
||||||
Disk based cache
|
Disk based cache
|
||||||
|
|
||||||
This is implemented as a shelve object and it is shared by multiple web2py
|
This is implemented as a key value store where each key corresponds to a
|
||||||
processes (and threads) as long as they share the same filesystem.
|
single file in disk which is replaced when the value changes.
|
||||||
|
|
||||||
Disk cache provides persistance when web2py is started/stopped but it slower
|
Disk cache provides persistance when web2py is started/stopped but it is
|
||||||
than `CacheInRam`
|
slower than `CacheInRam`
|
||||||
|
|
||||||
Values stored in disk cache must be pickable.
|
Values stored in disk cache must be pickable.
|
||||||
"""
|
"""
|
||||||
@@ -259,8 +259,11 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
"""
|
"""
|
||||||
Implements a key based storage in disk.
|
Implements a key based storage in disk.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, folder):
|
def __init__(self, folder):
|
||||||
self.folder = folder
|
self.folder = folder
|
||||||
|
self.key_filter_in = lambda key: key
|
||||||
|
self.key_filter_out = lambda key: key
|
||||||
# Check the best way to do atomic file replacement.
|
# Check the best way to do atomic file replacement.
|
||||||
if sys.version_info >= (3, 3):
|
if sys.version_info >= (3, 3):
|
||||||
self.replace = os.replace
|
self.replace = os.replace
|
||||||
@@ -279,6 +282,17 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def replace_windows(src, dst):
|
def replace_windows(src, dst):
|
||||||
|
"""
|
||||||
|
The Windows filesystem has a 256 character limit for the filename.
|
||||||
|
To use filenames longer than that, the '\\?\' prefix needs to be used.
|
||||||
|
By default, this prefix is added to all windows filenames,
|
||||||
|
when accessing it.
|
||||||
|
View this for details: http://stackoverflow.com/a/23230380/348142
|
||||||
|
"""
|
||||||
|
windows_prefix = "\\\\?\\"
|
||||||
|
dst = windows_prefix + dst
|
||||||
|
src = windows_prefix + src
|
||||||
|
|
||||||
if not ReplaceFile(dst, src, None, 0, 0, 0):
|
if not ReplaceFile(dst, src, None, 0, 0, 0):
|
||||||
os.rename(src, dst)
|
os.rename(src, dst)
|
||||||
|
|
||||||
@@ -287,6 +301,25 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
# POSIX rename() is always atomic
|
# POSIX rename() is always atomic
|
||||||
self.replace = os.rename
|
self.replace = os.rename
|
||||||
|
|
||||||
|
# Make sure we use valid filenames.
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import base64
|
||||||
|
def key_filter_in_windows(key):
|
||||||
|
"""
|
||||||
|
Windows doesn't allow \ / : * ? "< > | in filenames.
|
||||||
|
To go around this encode the keys with base32.
|
||||||
|
"""
|
||||||
|
return base64.b32encode(key)
|
||||||
|
|
||||||
|
def key_filter_out_windows(key):
|
||||||
|
"""
|
||||||
|
We need to decode the keys so regex based removal works.
|
||||||
|
"""
|
||||||
|
return base64.b32decode(key)
|
||||||
|
|
||||||
|
self.key_filter_in = key_filter_in_windows
|
||||||
|
self.key_filter_out = key_filter_out_windows
|
||||||
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
tmp_name, tmp_path = tempfile.mkstemp(dir=self.folder)
|
tmp_name, tmp_path = tempfile.mkstemp(dir=self.folder)
|
||||||
@@ -295,6 +328,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
pickle.dump((time.time(), value), tmp, pickle.HIGHEST_PROTOCOL)
|
pickle.dump((time.time(), value), tmp, pickle.HIGHEST_PROTOCOL)
|
||||||
finally:
|
finally:
|
||||||
tmp.close()
|
tmp.close()
|
||||||
|
key = self.key_filter_in(key)
|
||||||
fullfilename = os.path.join(self.folder, recfile.generate(key))
|
fullfilename = os.path.join(self.folder, recfile.generate(key))
|
||||||
if not os.path.exists(os.path.dirname(fullfilename)):
|
if not os.path.exists(os.path.dirname(fullfilename)):
|
||||||
os.makedirs(os.path.dirname(fullfilename))
|
os.makedirs(os.path.dirname(fullfilename))
|
||||||
@@ -302,27 +336,33 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
key = self.key_filter_in(key)
|
||||||
if recfile.exists(key, path=self.folder):
|
if recfile.exists(key, path=self.folder):
|
||||||
timestamp, value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
timestamp, value = pickle.load(recfile.open(key, 'rb', path=self.folder))
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
|
key = self.key_filter_in(key)
|
||||||
return recfile.exists(key, path=self.folder)
|
return recfile.exists(key, path=self.folder)
|
||||||
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
|
key = self.key_filter_in(key)
|
||||||
recfile.remove(key, path=self.folder)
|
recfile.remove(key, path=self.folder)
|
||||||
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for dirpath, dirnames, filenames in os.walk(self.folder):
|
for dirpath, dirnames, filenames in os.walk(self.folder):
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
yield filename
|
yield self.key_filter_out(filename)
|
||||||
|
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
return [filename for dirpath, dirnames, filenames in os.walk(self.folder) for filename in filenames]
|
return list(self.__iter__())
|
||||||
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
try:
|
try:
|
||||||
@@ -335,6 +375,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
for key in self:
|
for key in self:
|
||||||
del self[key]
|
del self[key]
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, request=None, folder=None):
|
def __init__(self, request=None, folder=None):
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
self.request = request
|
self.request = request
|
||||||
@@ -389,6 +430,7 @@ class CacheOnDisk(CacheAbstract):
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def clear(self, regex=None):
|
def clear(self, regex=None):
|
||||||
self.initialize()
|
self.initialize()
|
||||||
storage = self.storage
|
storage = self.storage
|
||||||
|
|||||||
+3
-3
@@ -26,7 +26,7 @@ from gluon.fileutils import mktree, listdir, read_file, write_file
|
|||||||
from gluon.myregex import regex_expose, regex_longcomments
|
from gluon.myregex import regex_expose, regex_longcomments
|
||||||
from gluon.languages import translator
|
from gluon.languages import translator
|
||||||
from gluon.dal import DAL, Field
|
from gluon.dal import DAL, Field
|
||||||
from gluon.dal.base import BaseAdapter
|
from pydal.base import BaseAdapter
|
||||||
from gluon.sqlhtml import SQLFORM, SQLTABLE
|
from gluon.sqlhtml import SQLFORM, SQLTABLE
|
||||||
from gluon.cache import Cache
|
from gluon.cache import Cache
|
||||||
from gluon.globals import current, Response
|
from gluon.globals import current, Response
|
||||||
@@ -127,7 +127,7 @@ class mybuiltin(object):
|
|||||||
def LOAD(c=None, f='index', args=None, vars=None,
|
def LOAD(c=None, f='index', args=None, vars=None,
|
||||||
extension=None, target=None, ajax=False, ajax_trap=False,
|
extension=None, target=None, ajax=False, ajax_trap=False,
|
||||||
url=None, user_signature=False, timeout=None, times=1,
|
url=None, user_signature=False, timeout=None, times=1,
|
||||||
content='loading...', **attr):
|
content='loading...', post_vars=Storage(), **attr):
|
||||||
""" LOADs a component into the action's document
|
""" LOADs a component into the action's document
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -202,7 +202,7 @@ def LOAD(c=None, f='index', args=None, vars=None,
|
|||||||
other_request.args = List(args)
|
other_request.args = List(args)
|
||||||
other_request.vars = vars
|
other_request.vars = vars
|
||||||
other_request.get_vars = vars
|
other_request.get_vars = vars
|
||||||
other_request.post_vars = Storage()
|
other_request.post_vars = post_vars
|
||||||
other_response = Response()
|
other_response = Response()
|
||||||
other_request.env.path_info = '/' + \
|
other_request.env.path_info = '/' + \
|
||||||
'/'.join([request.application, c, f] +
|
'/'.join([request.application, c, f] +
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -340,4 +340,3 @@ example_policies = {
|
|||||||
'DELETE':{'query':None},
|
'DELETE':{'query':None},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class BrowserID(object):
|
|||||||
auth_info_json = fetch(self.verify_url, data=verify_data)
|
auth_info_json = fetch(self.verify_url, data=verify_data)
|
||||||
j = json.loads(auth_info_json)
|
j = json.loads(auth_info_json)
|
||||||
epoch_time = int(time.time() * 1000) # we need 13 digit epoch time
|
epoch_time = int(time.time() * 1000) # we need 13 digit epoch time
|
||||||
if j["status"] == "okay" and j["audience"] == audience and j['issuer'] == issuer and j['expires'] >= epoch_time:
|
if j["status"] == "okay" and j["audience"] == audience and j['issuer'].endswith(issuer) and j['expires'] >= epoch_time:
|
||||||
return dict(email=j['email'])
|
return dict(email=j['email'])
|
||||||
elif self.on_login_failure:
|
elif self.on_login_failure:
|
||||||
#print "status: ", j["status"]=="okay", j["status"]
|
#print "status: ", j["status"]=="okay", j["status"]
|
||||||
|
|||||||
@@ -252,4 +252,3 @@ class IMAP4(object):
|
|||||||
return Connection()
|
return Connection()
|
||||||
|
|
||||||
IMAP4_SSL = IMAP4
|
IMAP4_SSL = IMAP4
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ jQuery(function(){
|
|||||||
<label class="control-label">Card Number</label>
|
<label class="control-label">Card Number</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" size="20" data-stripe="number"
|
<input type="text" size="20" data-stripe="number"
|
||||||
placeholder="4242424242424242"/>
|
placeholder="4242424242424242"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ jQuery(function(){
|
|||||||
<label class="control-label">CVC</label>
|
<label class="control-label">CVC</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" size="4" style="width:80px" data-stripe="cvc"
|
<input type="text" size="4" style="width:80px" data-stripe="cvc"
|
||||||
placeholder="XXX"/>
|
placeholder="XXX"/>
|
||||||
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
|
<a href="http://en.wikipedia.org/wiki/Card_Verification_Code" target="_blank">What is this?</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,10 +209,10 @@ jQuery(function(){
|
|||||||
<label class="control-label">Expiration</label>
|
<label class="control-label">Expiration</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
|
<input type="text" size="2" style="width:40px" data-stripe="exp-month"
|
||||||
placeholder="MM"/>
|
placeholder="MM"/>
|
||||||
/
|
/
|
||||||
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
|
<input type="text" size="4" style="width:80px" data-stripe="exp-year"
|
||||||
placeholder="YYYY"/>
|
placeholder="YYYY"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Developed by Massimo Di Pierro
|
Developed by Massimo Di Pierro
|
||||||
Released under the web2py license (LGPL)
|
Released under the web2py license (LGPL)
|
||||||
|
|||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from pydal import DAL as pyDAL
|
||||||
|
from pydal import Field, SQLCustomType, geoPoint, geoLine, geoPolygon
|
||||||
|
|
||||||
|
|
||||||
|
def _default_validators(db, field):
|
||||||
|
"""
|
||||||
|
Field type validation, using web2py's validators mechanism.
|
||||||
|
|
||||||
|
makes sure the content of a field is in line with the declared
|
||||||
|
fieldtype
|
||||||
|
"""
|
||||||
|
from gluon import validators
|
||||||
|
field_type, field_length = field.type, field.length
|
||||||
|
requires = []
|
||||||
|
|
||||||
|
def ff(r, id):
|
||||||
|
row = r(id)
|
||||||
|
if not row:
|
||||||
|
return str(id)
|
||||||
|
elif hasattr(r, '_format') and isinstance(r._format, str):
|
||||||
|
return r._format % row
|
||||||
|
elif hasattr(r, '_format') and callable(r._format):
|
||||||
|
return r._format(row)
|
||||||
|
else:
|
||||||
|
return str(id)
|
||||||
|
|
||||||
|
if field_type in (('string', 'text', 'password')):
|
||||||
|
requires.append(validators.IS_LENGTH(field_length))
|
||||||
|
elif field_type == 'json':
|
||||||
|
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
|
||||||
|
elif field_type == 'double' or field_type == 'float':
|
||||||
|
requires.append(validators.IS_FLOAT_IN_RANGE(-1e100, 1e100))
|
||||||
|
elif field_type == 'integer':
|
||||||
|
requires.append(validators.IS_INT_IN_RANGE(-2**31, 2**31))
|
||||||
|
elif field_type == 'bigint':
|
||||||
|
requires.append(validators.IS_INT_IN_RANGE(-2**63, 2**63))
|
||||||
|
elif field_type.startswith('decimal'):
|
||||||
|
requires.append(validators.IS_DECIMAL_IN_RANGE(-10**10, 10**10))
|
||||||
|
elif field_type == 'date':
|
||||||
|
requires.append(validators.IS_DATE())
|
||||||
|
elif field_type == 'time':
|
||||||
|
requires.append(validators.IS_TIME())
|
||||||
|
elif field_type == 'datetime':
|
||||||
|
requires.append(validators.IS_DATETIME())
|
||||||
|
elif db and field_type.startswith('reference') and \
|
||||||
|
field_type.find('.') < 0 and \
|
||||||
|
field_type[10:] in db.tables:
|
||||||
|
referenced = db[field_type[10:]]
|
||||||
|
|
||||||
|
def repr_ref(id, row=None, r=referenced, f=ff):
|
||||||
|
return f(r, id)
|
||||||
|
|
||||||
|
field.represent = field.represent or repr_ref
|
||||||
|
if hasattr(referenced, '_format') and referenced._format:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced._id,
|
||||||
|
referenced._format)
|
||||||
|
if field.unique:
|
||||||
|
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||||
|
if field.tablename == field_type[10:]:
|
||||||
|
return validators.IS_EMPTY_OR(requires)
|
||||||
|
return requires
|
||||||
|
elif db and field_type.startswith('list:reference') and \
|
||||||
|
field_type.find('.') < 0 and \
|
||||||
|
field_type[15:] in db.tables:
|
||||||
|
referenced = db[field_type[15:]]
|
||||||
|
|
||||||
|
def list_ref_repr(ids, row=None, r=referenced, f=ff):
|
||||||
|
if not ids:
|
||||||
|
return None
|
||||||
|
from pydal.adapters import GoogleDatastoreAdapter
|
||||||
|
refs = None
|
||||||
|
db, id = r._db, r._id
|
||||||
|
if isinstance(db._adapter, GoogleDatastoreAdapter):
|
||||||
|
def count(values):
|
||||||
|
return db(id.belongs(values)).select(id)
|
||||||
|
rx = range(0, len(ids), 30)
|
||||||
|
refs = reduce(lambda a, b: a & b, [count(ids[i:i+30])
|
||||||
|
for i in rx])
|
||||||
|
else:
|
||||||
|
refs = db(id.belongs(ids)).select(id)
|
||||||
|
return (refs and ', '.join(f(r, x.id) for x in refs) or '')
|
||||||
|
|
||||||
|
field.represent = field.represent or list_ref_repr
|
||||||
|
if hasattr(referenced, '_format') and referenced._format:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced._id,
|
||||||
|
referenced._format, multiple=True)
|
||||||
|
else:
|
||||||
|
requires = validators.IS_IN_DB(db, referenced._id,
|
||||||
|
multiple=True)
|
||||||
|
if field.unique:
|
||||||
|
requires._and = validators.IS_NOT_IN_DB(db, field)
|
||||||
|
if not field.notnull:
|
||||||
|
requires = validators.IS_EMPTY_OR(requires)
|
||||||
|
return requires
|
||||||
|
elif field_type.startswith('list:'):
|
||||||
|
def repr_list(values, row=None):
|
||||||
|
return', '.join(str(v) for v in (values or []))
|
||||||
|
|
||||||
|
field.represent = field.represent or repr_list
|
||||||
|
|
||||||
|
if field.unique:
|
||||||
|
requires.append(validators.IS_NOT_IN_DB(db, field))
|
||||||
|
sff = ['in', 'do', 'da', 'ti', 'de', 'bo']
|
||||||
|
if field.notnull and not field_type[:2] in sff:
|
||||||
|
requires.append(validators.IS_NOT_EMPTY())
|
||||||
|
elif not field.notnull and field_type[:2] in sff and requires:
|
||||||
|
requires[0] = validators.IS_EMPTY_OR(requires[0])
|
||||||
|
return requires
|
||||||
|
|
||||||
|
from gluon import serializers as w2p_serializers
|
||||||
|
from gluon.utils import web2py_uuid
|
||||||
|
from gluon import sqlhtml
|
||||||
|
|
||||||
|
|
||||||
|
class DAL(pyDAL):
|
||||||
|
serializers = w2p_serializers
|
||||||
|
validators_method = _default_validators
|
||||||
|
uuid = lambda x: web2py_uuid()
|
||||||
|
representers = {
|
||||||
|
'rows_render': sqlhtml.represent,
|
||||||
|
'rows_xml': sqlhtml.SQLTABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
#: add web2py contrib drivers to pyDAL
|
||||||
|
from pydal.drivers import DRIVERS
|
||||||
|
if not DRIVERS.get('pymysql'):
|
||||||
|
from .contrib import pymysql
|
||||||
|
DRIVERS['pymysql'] = pymysql
|
||||||
|
#if not DRIVERS.get('pg8000'):
|
||||||
|
# from .contrib import pg8000
|
||||||
|
# DRIVERS['pg8000'] = pg8000
|
||||||
|
if not DRIVERS.get('pyodbc'):
|
||||||
|
from .contrib import pypyodbc as pyodbc
|
||||||
|
DRIVERS['pyodbc'] = pyodbc
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from .base import DAL
|
|
||||||
from .objects import Field
|
|
||||||
from .helpers.classes import SQLCustomType
|
|
||||||
from .helpers.methods import geoPoint, geoLine, geoPolygon
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import sys
|
|
||||||
import hashlib
|
|
||||||
import os
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
import cPickle as pickle
|
|
||||||
import cStringIO as StringIO
|
|
||||||
import copy_reg as copyreg
|
|
||||||
hashlib_md5 = hashlib.md5
|
|
||||||
else:
|
|
||||||
import pickle
|
|
||||||
from io import StringIO
|
|
||||||
import copyreg
|
|
||||||
hashlib_md5 = lambda s: hashlib.md5(bytes(s,'utf8'))
|
|
||||||
|
|
||||||
pjoin = os.path.join
|
|
||||||
exists = os.path.exists
|
|
||||||
ogetattr = object.__getattribute__
|
|
||||||
osetattr = object.__setattr__
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import threading
|
|
||||||
import logging
|
|
||||||
|
|
||||||
GLOBAL_LOCKER = threading.RLock()
|
|
||||||
THREAD_LOCAL = threading.local()
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger("web2py.dal")
|
|
||||||
|
|
||||||
DEFAULT = lambda: None
|
|
||||||
|
|
||||||
def IDENTITY(x): return x
|
|
||||||
def OR(a,b): return a|b
|
|
||||||
def AND(a,b): return a&b
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import decimal
|
|
||||||
import re
|
|
||||||
|
|
||||||
from ._globals import LOGGER
|
|
||||||
|
|
||||||
|
|
||||||
# verify presence of web2py modules
|
|
||||||
try:
|
|
||||||
from collections import OrderedDict
|
|
||||||
except:
|
|
||||||
from gluon.contrib.ordereddict import OrderedDict
|
|
||||||
|
|
||||||
try:
|
|
||||||
from gluon.utils import web2py_uuid
|
|
||||||
except (ImportError, SystemError):
|
|
||||||
import uuid
|
|
||||||
def web2py_uuid(): return str(uuid.uuid4())
|
|
||||||
|
|
||||||
try:
|
|
||||||
import portalocker
|
|
||||||
have_portalocker = True
|
|
||||||
except ImportError:
|
|
||||||
portalocker = None
|
|
||||||
have_portalocker = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
from gluon import serializers
|
|
||||||
have_serializers = True
|
|
||||||
simplejson = None
|
|
||||||
except ImportError:
|
|
||||||
serializers = None
|
|
||||||
have_serializers = False
|
|
||||||
try:
|
|
||||||
import json as simplejson
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
import gluon.contrib.simplejson as simplejson
|
|
||||||
except ImportError:
|
|
||||||
simplejson = None
|
|
||||||
|
|
||||||
|
|
||||||
# list of drivers will be built on the fly
|
|
||||||
# and lists only what is available
|
|
||||||
DRIVERS = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
from new import classobj
|
|
||||||
from google.appengine.ext import db as gae
|
|
||||||
from google.appengine.ext import ndb
|
|
||||||
from google.appengine.api import namespace_manager, rdbms
|
|
||||||
from google.appengine.api.datastore_types import Key ### for belongs on ID
|
|
||||||
from google.appengine.ext.db.polymodel import PolyModel
|
|
||||||
from google.appengine.ext.ndb.polymodel import PolyModel as NDBPolyModel
|
|
||||||
DRIVERS.append('google')
|
|
||||||
except ImportError:
|
|
||||||
classobj = None
|
|
||||||
gae = None
|
|
||||||
ndb = None
|
|
||||||
namespace_manager = rdbms = None
|
|
||||||
Key = None
|
|
||||||
PolyModel = NDBPolyModel = None
|
|
||||||
|
|
||||||
if not 'google' in DRIVERS:
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pysqlite2 import dbapi2 as sqlite2
|
|
||||||
DRIVERS.append('sqlite2')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no SQLite drivers pysqlite2.dbapi2')
|
|
||||||
|
|
||||||
try:
|
|
||||||
from sqlite3 import dbapi2 as sqlite3
|
|
||||||
DRIVERS.append('sqlite3')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no SQLite drivers sqlite3')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# first try contrib driver, then from site-packages (if installed)
|
|
||||||
try:
|
|
||||||
import gluon.contrib.pymysql as pymysql
|
|
||||||
# monkeypatch pymysql because they havent fixed the bug:
|
|
||||||
# https://github.com/petehunt/PyMySQL/issues/86
|
|
||||||
pymysql.ESCAPE_REGEX = re.compile("'")
|
|
||||||
pymysql.ESCAPE_MAP = {"'": "''"}
|
|
||||||
# end monkeypatch
|
|
||||||
except ImportError:
|
|
||||||
import pymysql
|
|
||||||
DRIVERS.append('pymysql')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no MySQL driver pymysql')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import MySQLdb
|
|
||||||
DRIVERS.append('MySQLdb')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no MySQL driver MySQLDB')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import mysql.connector as mysqlconnector
|
|
||||||
DRIVERS.append("mysqlconnector")
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug("no driver mysql.connector")
|
|
||||||
|
|
||||||
try:
|
|
||||||
import psycopg2
|
|
||||||
from psycopg2.extensions import adapt as psycopg2_adapt
|
|
||||||
DRIVERS.append('psycopg2')
|
|
||||||
except ImportError:
|
|
||||||
psycopg2_adapt = None
|
|
||||||
LOGGER.debug('no PostgreSQL driver psycopg2')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# first try contrib driver, then from site-packages (if installed)
|
|
||||||
try:
|
|
||||||
import gluon.contrib.pg8000.dbapi as pg8000
|
|
||||||
except ImportError:
|
|
||||||
import pg8000.dbapi as pg8000
|
|
||||||
DRIVERS.append('pg8000')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no PostgreSQL driver pg8000')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import cx_Oracle
|
|
||||||
DRIVERS.append('cx_Oracle')
|
|
||||||
except ImportError:
|
|
||||||
cx_Oracle = None
|
|
||||||
LOGGER.debug('no Oracle driver cx_Oracle')
|
|
||||||
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
import pyodbc
|
|
||||||
except ImportError:
|
|
||||||
try:
|
|
||||||
import gluon.contrib.pypyodbc as pyodbc
|
|
||||||
except Exception, e:
|
|
||||||
raise ImportError(str(e))
|
|
||||||
DRIVERS.append('pyodbc')
|
|
||||||
#DRIVERS.append('DB2(pyodbc)')
|
|
||||||
#DRIVERS.append('Teradata(pyodbc)')
|
|
||||||
#DRIVERS.append('Ingres(pyodbc)')
|
|
||||||
except ImportError:
|
|
||||||
pyodbc = None
|
|
||||||
LOGGER.debug('no MSSQL/DB2/Teradata/Ingres driver pyodbc')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import ibm_db_dbi
|
|
||||||
DRIVERS.append('ibm_db_dbi')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no DB2 driver ibm_db_dbi')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import Sybase
|
|
||||||
DRIVERS.append('Sybase')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no Sybase driver')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import kinterbasdb
|
|
||||||
DRIVERS.append('kinterbasdb')
|
|
||||||
#DRIVERS.append('Firebird(kinterbasdb)')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no Firebird/Interbase driver kinterbasdb')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import fdb
|
|
||||||
DRIVERS.append('fdb')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no Firebird driver fdb')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import firebirdsql
|
|
||||||
DRIVERS.append('firebirdsql')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no Firebird driver firebirdsql')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import informixdb
|
|
||||||
DRIVERS.append('informixdb')
|
|
||||||
LOGGER.warning('Informix support is experimental')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no Informix driver informixdb')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import sapdb
|
|
||||||
DRIVERS.append('sapdb')
|
|
||||||
LOGGER.warning('SAPDB support is experimental')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no SAP driver sapdb')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import cubriddb
|
|
||||||
DRIVERS.append('cubriddb')
|
|
||||||
LOGGER.warning('Cubrid support is experimental')
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no Cubrid driver cubriddb')
|
|
||||||
|
|
||||||
try:
|
|
||||||
from com.ziclix.python.sql import zxJDBC
|
|
||||||
import java.sql
|
|
||||||
# Try sqlite jdbc driver from http://www.zentus.com/sqlitejdbc/
|
|
||||||
from org.sqlite import JDBC # required by java.sql; ensure we have it
|
|
||||||
zxJDBC_sqlite = java.sql.DriverManager
|
|
||||||
DRIVERS.append('zxJDBC')
|
|
||||||
#DRIVERS.append('SQLite(zxJDBC)')
|
|
||||||
LOGGER.warning('zxJDBC support is experimental')
|
|
||||||
is_jdbc = True
|
|
||||||
except ImportError:
|
|
||||||
LOGGER.debug('no SQLite/PostgreSQL driver zxJDBC')
|
|
||||||
is_jdbc = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
import couchdb
|
|
||||||
DRIVERS.append('couchdb')
|
|
||||||
except ImportError:
|
|
||||||
couchdb = None
|
|
||||||
LOGGER.debug('no Couchdb driver couchdb')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import pymongo
|
|
||||||
DRIVERS.append('pymongo')
|
|
||||||
except:
|
|
||||||
LOGGER.debug('no MongoDB driver pymongo')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import imaplib
|
|
||||||
DRIVERS.append('imaplib')
|
|
||||||
except:
|
|
||||||
LOGGER.debug('no IMAP driver imaplib')
|
|
||||||
|
|
||||||
GAEDecimalProperty = None
|
|
||||||
NDBDecimalProperty = None
|
|
||||||
else:
|
|
||||||
is_jdbc = False
|
|
||||||
|
|
||||||
class GAEDecimalProperty(gae.Property):
|
|
||||||
"""
|
|
||||||
GAE decimal implementation
|
|
||||||
"""
|
|
||||||
data_type = decimal.Decimal
|
|
||||||
|
|
||||||
def __init__(self, precision, scale, **kwargs):
|
|
||||||
super(GAEDecimalProperty, self).__init__(self, **kwargs)
|
|
||||||
d = '1.'
|
|
||||||
for x in range(scale):
|
|
||||||
d += '0'
|
|
||||||
self.round = decimal.Decimal(d)
|
|
||||||
|
|
||||||
def get_value_for_datastore(self, model_instance):
|
|
||||||
value = super(GAEDecimalProperty, self)\
|
|
||||||
.get_value_for_datastore(model_instance)
|
|
||||||
if value is None or value == '':
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
def make_value_from_datastore(self, value):
|
|
||||||
if value is None or value == '':
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return decimal.Decimal(value).quantize(self.round)
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
value = super(GAEDecimalProperty, self).validate(value)
|
|
||||||
if value is None or isinstance(value, decimal.Decimal):
|
|
||||||
return value
|
|
||||||
elif isinstance(value, basestring):
|
|
||||||
return decimal.Decimal(value)
|
|
||||||
raise gae.BadValueError("Property %s must be a Decimal or string."\
|
|
||||||
% self.name)
|
|
||||||
|
|
||||||
#TODO Needs more testing
|
|
||||||
class NDBDecimalProperty(ndb.StringProperty):
|
|
||||||
"""
|
|
||||||
NDB decimal implementation
|
|
||||||
"""
|
|
||||||
data_type = decimal.Decimal
|
|
||||||
|
|
||||||
def __init__(self, precision, scale, **kwargs):
|
|
||||||
d = '1.'
|
|
||||||
for x in range(scale):
|
|
||||||
d += '0'
|
|
||||||
self.round = decimal.Decimal(d)
|
|
||||||
|
|
||||||
def _to_base_type(self, value):
|
|
||||||
if value is None or value == '':
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
def _from_base_type(self, value):
|
|
||||||
if value is None or value == '':
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return decimal.Decimal(value).quantize(self.round)
|
|
||||||
|
|
||||||
def _validate(self, value):
|
|
||||||
if value is None or isinstance(value, decimal.Decimal):
|
|
||||||
return value
|
|
||||||
elif isinstance(value, basestring):
|
|
||||||
return decimal.Decimal(value)
|
|
||||||
raise TypeError("Property %s must be a Decimal or string."\
|
|
||||||
% self._name)
|
|
||||||
|
|
||||||
psycopg2_adapt = None
|
|
||||||
cx_Oracle = None
|
|
||||||
pyodbc = None
|
|
||||||
couchdb = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_driver(name):
|
|
||||||
return globals().get(name)
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from .sqlite import SQLiteAdapter, SpatiaLiteAdapter, JDBCSQLiteAdapter
|
|
||||||
from .mysql import MySQLAdapter
|
|
||||||
from .postgres import PostgreSQLAdapter, NewPostgreSQLAdapter, JDBCPostgreSQLAdapter
|
|
||||||
from .oracle import OracleAdapter
|
|
||||||
from .mssql import MSSQLAdapter, MSSQL2Adapter, MSSQL3Adapter, MSSQL4Adapter, \
|
|
||||||
VerticaAdapter, SybaseAdapter
|
|
||||||
from .firebird import FireBirdAdapter
|
|
||||||
from .informix import InformixAdapter, InformixSEAdapter
|
|
||||||
from .db2 import DB2Adapter
|
|
||||||
from .teradata import TeradataAdapter
|
|
||||||
from .ingres import IngresAdapter, IngresUnicodeAdapter
|
|
||||||
from .sapdb import SAPDBAdapter
|
|
||||||
from .cubrid import CubridAdapter
|
|
||||||
from .google import GoogleDatastoreAdapter, GoogleSQLAdapter
|
|
||||||
from .couchdb import CouchDBAdapter
|
|
||||||
from .mongo import MongoDBAdapter
|
|
||||||
from .imap import IMAPAdapter
|
|
||||||
|
|
||||||
|
|
||||||
ADAPTERS = {
|
|
||||||
'sqlite': SQLiteAdapter,
|
|
||||||
'spatialite': SpatiaLiteAdapter,
|
|
||||||
'sqlite:memory': SQLiteAdapter,
|
|
||||||
'spatialite:memory': SpatiaLiteAdapter,
|
|
||||||
'mysql': MySQLAdapter,
|
|
||||||
'postgres': PostgreSQLAdapter,
|
|
||||||
'postgres:psycopg2': PostgreSQLAdapter,
|
|
||||||
'postgres:pg8000': PostgreSQLAdapter,
|
|
||||||
'postgres2:psycopg2': NewPostgreSQLAdapter,
|
|
||||||
'postgres2:pg8000': NewPostgreSQLAdapter,
|
|
||||||
'oracle': OracleAdapter,
|
|
||||||
'mssql': MSSQLAdapter,
|
|
||||||
'mssql2': MSSQL2Adapter,
|
|
||||||
'mssql3': MSSQL3Adapter,
|
|
||||||
'mssql4' : MSSQL4Adapter,
|
|
||||||
'vertica': VerticaAdapter,
|
|
||||||
'sybase': SybaseAdapter,
|
|
||||||
'db2:ibm_db_dbi': DB2Adapter,
|
|
||||||
'db2:pyodbc': DB2Adapter,
|
|
||||||
'teradata': TeradataAdapter,
|
|
||||||
'informix': InformixAdapter,
|
|
||||||
'informix-se': InformixSEAdapter,
|
|
||||||
'firebird': FireBirdAdapter,
|
|
||||||
'firebird_embedded': FireBirdAdapter,
|
|
||||||
'ingres': IngresAdapter,
|
|
||||||
'ingresu': IngresUnicodeAdapter,
|
|
||||||
'sapdb': SAPDBAdapter,
|
|
||||||
'cubrid': CubridAdapter,
|
|
||||||
'jdbc:sqlite': JDBCSQLiteAdapter,
|
|
||||||
'jdbc:sqlite:memory': JDBCSQLiteAdapter,
|
|
||||||
'jdbc:postgres': JDBCPostgreSQLAdapter,
|
|
||||||
'gae': GoogleDatastoreAdapter, # discouraged, for backward compatibility
|
|
||||||
'google:datastore': GoogleDatastoreAdapter,
|
|
||||||
'google:datastore+ndb': GoogleDatastoreAdapter,
|
|
||||||
'google:sql': GoogleSQLAdapter,
|
|
||||||
'couchdb': CouchDBAdapter,
|
|
||||||
'mongodb': MongoDBAdapter,
|
|
||||||
'imap': IMAPAdapter
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,202 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .._load import serializers, couchdb, web2py_uuid
|
|
||||||
from ..objects import Field, Query
|
|
||||||
from ..helpers.classes import SQLALL
|
|
||||||
from ..helpers.methods import uuid2int
|
|
||||||
from .base import BaseAdapter, NoSQLAdapter, SELECT_ARGS
|
|
||||||
|
|
||||||
|
|
||||||
class CouchDBAdapter(NoSQLAdapter):
|
|
||||||
drivers = ('couchdb',)
|
|
||||||
|
|
||||||
uploads_in_blob = True
|
|
||||||
types = {
|
|
||||||
'boolean': bool,
|
|
||||||
'string': str,
|
|
||||||
'text': str,
|
|
||||||
'json': str,
|
|
||||||
'password': str,
|
|
||||||
'blob': str,
|
|
||||||
'upload': str,
|
|
||||||
'integer': long,
|
|
||||||
'bigint': long,
|
|
||||||
'float': float,
|
|
||||||
'double': float,
|
|
||||||
'date': datetime.date,
|
|
||||||
'time': datetime.time,
|
|
||||||
'datetime': datetime.datetime,
|
|
||||||
'id': long,
|
|
||||||
'reference': long,
|
|
||||||
'list:string': list,
|
|
||||||
'list:integer': list,
|
|
||||||
'list:reference': list,
|
|
||||||
}
|
|
||||||
|
|
||||||
def file_exists(self, filename): pass
|
|
||||||
def file_open(self, filename, mode='rb', lock=True): pass
|
|
||||||
def file_close(self, fileobj): pass
|
|
||||||
|
|
||||||
def expand(self,expression,field_type=None):
|
|
||||||
if isinstance(expression,Field):
|
|
||||||
if expression.type=='id':
|
|
||||||
return "%s._id" % expression.tablename
|
|
||||||
return BaseAdapter.expand(self,expression,field_type)
|
|
||||||
|
|
||||||
def AND(self,first,second):
|
|
||||||
return '(%s && %s)' % (self.expand(first),self.expand(second))
|
|
||||||
|
|
||||||
def OR(self,first,second):
|
|
||||||
return '(%s || %s)' % (self.expand(first),self.expand(second))
|
|
||||||
|
|
||||||
def EQ(self,first,second):
|
|
||||||
if second is None:
|
|
||||||
return '(%s == null)' % self.expand(first)
|
|
||||||
return '(%s == %s)' % (self.expand(first),self.expand(second,first.type))
|
|
||||||
|
|
||||||
def NE(self,first,second):
|
|
||||||
if second is None:
|
|
||||||
return '(%s != null)' % self.expand(first)
|
|
||||||
return '(%s != %s)' % (self.expand(first),self.expand(second,first.type))
|
|
||||||
|
|
||||||
def COMMA(self,first,second):
|
|
||||||
return '%s + %s' % (self.expand(first),self.expand(second))
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
value = NoSQLAdapter.represent(self, obj, fieldtype)
|
|
||||||
if fieldtype=='id':
|
|
||||||
return repr(str(long(value)))
|
|
||||||
elif fieldtype in ('date','time','datetime','boolean'):
|
|
||||||
return serializers.json(value)
|
|
||||||
return repr(not isinstance(value,unicode) and value \
|
|
||||||
or value and value.encode('utf8'))
|
|
||||||
|
|
||||||
def __init__(self,db,uri='couchdb://127.0.0.1:5984',
|
|
||||||
pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args)
|
|
||||||
self.dbengine = 'couchdb'
|
|
||||||
self.folder = folder
|
|
||||||
db['_lastsql'] = ''
|
|
||||||
self.db_codec = 'UTF-8'
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.pool_size = pool_size
|
|
||||||
|
|
||||||
url='http://'+uri[10:]
|
|
||||||
def connector(url=url,driver_args=driver_args):
|
|
||||||
return self.driver.Server(url,**driver_args)
|
|
||||||
self.reconnect(connector,cursor=False)
|
|
||||||
|
|
||||||
def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None):
|
|
||||||
if migrate:
|
|
||||||
try:
|
|
||||||
self.connection.create(table._tablename)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def insert(self,table,fields):
|
|
||||||
id = uuid2int(web2py_uuid())
|
|
||||||
ctable = self.connection[table._tablename]
|
|
||||||
values = dict((k.name,self.represent(v,k.type)) for k,v in fields)
|
|
||||||
values['_id'] = str(id)
|
|
||||||
ctable.save(values)
|
|
||||||
return id
|
|
||||||
|
|
||||||
def _select(self,query,fields,attributes):
|
|
||||||
if not isinstance(query,Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
for key in set(attributes.keys())-SELECT_ARGS:
|
|
||||||
raise SyntaxError('invalid select attribute: %s' % key)
|
|
||||||
new_fields=[]
|
|
||||||
for item in fields:
|
|
||||||
if isinstance(item,SQLALL):
|
|
||||||
new_fields += item._table
|
|
||||||
else:
|
|
||||||
new_fields.append(item)
|
|
||||||
def uid(fd):
|
|
||||||
return fd=='id' and '_id' or fd
|
|
||||||
def get(row,fd):
|
|
||||||
return fd=='id' and long(row['_id']) or row.get(fd,None)
|
|
||||||
fields = new_fields
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
fieldnames = [f.name for f in (fields or self.db[tablename])]
|
|
||||||
colnames = ['%s.%s' % (tablename,k) for k in fieldnames]
|
|
||||||
fields = ','.join(['%s.%s' % (tablename,uid(f)) for f in fieldnames])
|
|
||||||
fn="(function(%(t)s){if(%(query)s)emit(%(order)s,[%(fields)s]);})" %\
|
|
||||||
dict(t=tablename,
|
|
||||||
query=self.expand(query),
|
|
||||||
order='%s._id' % tablename,
|
|
||||||
fields=fields)
|
|
||||||
return fn, colnames
|
|
||||||
|
|
||||||
def select(self,query,fields,attributes):
|
|
||||||
if not isinstance(query,Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
fn, colnames = self._select(query,fields,attributes)
|
|
||||||
tablename = colnames[0].split('.')[0]
|
|
||||||
ctable = self.connection[tablename]
|
|
||||||
rows = [cols['value'] for cols in ctable.query(fn)]
|
|
||||||
processor = attributes.get('processor',self.parse)
|
|
||||||
return processor(rows,fields,colnames,False)
|
|
||||||
|
|
||||||
def delete(self,tablename,query):
|
|
||||||
if not isinstance(query,Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
if query.first.type=='id' and query.op==self.EQ:
|
|
||||||
id = query.second
|
|
||||||
tablename = query.first.tablename
|
|
||||||
assert(tablename == query.first.tablename)
|
|
||||||
ctable = self.connection[tablename]
|
|
||||||
try:
|
|
||||||
del ctable[str(id)]
|
|
||||||
return 1
|
|
||||||
except couchdb.http.ResourceNotFound:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
rows = self.select(query,[self.db[tablename]._id],{})
|
|
||||||
ctable = self.connection[tablename]
|
|
||||||
for row in rows:
|
|
||||||
del ctable[str(row.id)]
|
|
||||||
return len(rows)
|
|
||||||
|
|
||||||
def update(self,tablename,query,fields):
|
|
||||||
if not isinstance(query,Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
if query.first.type=='id' and query.op==self.EQ:
|
|
||||||
id = query.second
|
|
||||||
tablename = query.first.tablename
|
|
||||||
ctable = self.connection[tablename]
|
|
||||||
try:
|
|
||||||
doc = ctable[str(id)]
|
|
||||||
for key,value in fields:
|
|
||||||
doc[key.name] = self.represent(value,self.db[tablename][key.name].type)
|
|
||||||
ctable.save(doc)
|
|
||||||
return 1
|
|
||||||
except couchdb.http.ResourceNotFound:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
rows = self.select(query,[self.db[tablename]._id],{})
|
|
||||||
ctable = self.connection[tablename]
|
|
||||||
table = self.db[tablename]
|
|
||||||
for row in rows:
|
|
||||||
doc = ctable[str(row.id)]
|
|
||||||
for key,value in fields:
|
|
||||||
doc[key.name] = self.represent(value,table[key.name].type)
|
|
||||||
ctable.save(doc)
|
|
||||||
return len(rows)
|
|
||||||
|
|
||||||
def count(self,query,distinct=None):
|
|
||||||
if distinct:
|
|
||||||
raise RuntimeError("COUNT DISTINCT not supported")
|
|
||||||
if not isinstance(query,Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
rows = self.select(query,[self.db[tablename]._id],{})
|
|
||||||
return len(rows)
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .mysql import MySQLAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class CubridAdapter(MySQLAdapter):
|
|
||||||
drivers = ('cubriddb',)
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$')
|
|
||||||
|
|
||||||
def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "cubrid"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
"Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
port = int(m.group('port') or '30000')
|
|
||||||
user = credential_decoder(user)
|
|
||||||
passwd = credential_decoder(password)
|
|
||||||
def connector(host=host,port=port,db=db,
|
|
||||||
user=user,passwd=passwd,driver_args=driver_args):
|
|
||||||
return self.driver.connect(host,port,db,user,passwd,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.execute('SET FOREIGN_KEY_CHECKS=1;')
|
|
||||||
self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
|
|
||||||
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import base64
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class DB2Adapter(BaseAdapter):
|
|
||||||
drivers = ('ibm_db_dbi', 'pyodbc')
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'CLOB',
|
|
||||||
'json': 'CLOB',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BLOB',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'REAL',
|
|
||||||
'double': 'DOUBLE',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'TIMESTAMP',
|
|
||||||
'id': 'INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL',
|
|
||||||
'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'CLOB',
|
|
||||||
'list:string': 'CLOB',
|
|
||||||
'list:reference': 'CLOB',
|
|
||||||
'big-id': 'BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL',
|
|
||||||
'big-reference': 'BIGINT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def LEFT_JOIN(self):
|
|
||||||
return 'LEFT OUTER JOIN'
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'RAND()'
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
sql_o += ' FETCH FIRST %i ROWS ONLY' % lmax
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def represent_exceptions(self, obj, fieldtype):
|
|
||||||
if fieldtype == 'blob':
|
|
||||||
obj = base64.b64encode(str(obj))
|
|
||||||
return "BLOB('%s')" % obj
|
|
||||||
elif fieldtype == 'datetime':
|
|
||||||
if isinstance(obj, datetime.datetime):
|
|
||||||
obj = obj.isoformat()[:19].replace('T','-').replace(':','.')
|
|
||||||
elif isinstance(obj, datetime.date):
|
|
||||||
obj = obj.isoformat()[:10]+'-00.00.00'
|
|
||||||
return "'%s'" % obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "db2"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://', 1)[1]
|
|
||||||
|
|
||||||
def connector(cnxn=ruri,driver_args=driver_args):
|
|
||||||
if self.driver_name == 'ibm_db_dbi':
|
|
||||||
vars = cnxn.split(";")
|
|
||||||
cnxn = {}
|
|
||||||
for var in vars:
|
|
||||||
v = var.split('=')
|
|
||||||
cnxn[v[0].lower()] = v[1]
|
|
||||||
return self.driver.connect(cnxn['dsn'], cnxn['uid'], cnxn['pwd'], **driver_args)
|
|
||||||
else:
|
|
||||||
return self.driver.connect(cnxn, **driver_args)
|
|
||||||
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def execute(self,command,placeholders=None):
|
|
||||||
if command[-1:]==';':
|
|
||||||
command = command[:-1]
|
|
||||||
if placeholders:
|
|
||||||
return self.log_execute(command, placeholders)
|
|
||||||
return self.log_execute(command)
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
self.execute('SELECT DISTINCT IDENTITY_VAL_LOCAL() FROM %s;' % table)
|
|
||||||
return long(self.cursor.fetchone()[0])
|
|
||||||
|
|
||||||
def rowslice(self,rows,minimum=0,maximum=None):
|
|
||||||
if maximum is None:
|
|
||||||
return rows[minimum:]
|
|
||||||
return rows[minimum:maximum]
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from ..objects import Expression
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class FireBirdAdapter(BaseAdapter):
|
|
||||||
drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
|
|
||||||
|
|
||||||
commit_on_alter_table = False
|
|
||||||
support_distributed_transaction = True
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'BLOB SUB_TYPE 1',
|
|
||||||
'json': 'BLOB SUB_TYPE 1',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BLOB SUB_TYPE 0',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INTEGER',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'DOUBLE PRECISION',
|
|
||||||
'decimal': 'DECIMAL(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'TIMESTAMP',
|
|
||||||
'id': 'INTEGER PRIMARY KEY',
|
|
||||||
'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'BLOB SUB_TYPE 1',
|
|
||||||
'list:string': 'BLOB SUB_TYPE 1',
|
|
||||||
'list:reference': 'BLOB SUB_TYPE 1',
|
|
||||||
'big-id': 'BIGINT PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def sequence_name(self,tablename):
|
|
||||||
return ('genid_' + self.QUOTE_TEMPLATE) % tablename
|
|
||||||
|
|
||||||
def trigger_name(self,tablename):
|
|
||||||
return 'trg_id_%s' % tablename
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'RAND()'
|
|
||||||
|
|
||||||
def EPOCH(self, first):
|
|
||||||
return "DATEDIFF(second, '1970-01-01 00:00:00', %s)" % self.expand(first)
|
|
||||||
|
|
||||||
def NOT_NULL(self,default,field_type):
|
|
||||||
return 'DEFAULT %s NOT NULL' % self.represent(default,field_type)
|
|
||||||
|
|
||||||
def SUBSTRING(self,field,parameters):
|
|
||||||
return 'SUBSTRING(%s from %s for %s)' % (self.expand(field), parameters[0], parameters[1])
|
|
||||||
|
|
||||||
def LENGTH(self, first):
|
|
||||||
return "CHAR_LENGTH(%s)" % self.expand(first)
|
|
||||||
|
|
||||||
def CONTAINS(self,first,second,case_sensitive=False):
|
|
||||||
if first.type.startswith('list:'):
|
|
||||||
second = Expression(None,self.CONCAT('|',Expression(
|
|
||||||
None,self.REPLACE(second,('|','||'))),'|'))
|
|
||||||
return '(%s CONTAINING %s)' % (self.expand(first),
|
|
||||||
self.expand(second, 'string'))
|
|
||||||
|
|
||||||
def _drop(self,table,mode):
|
|
||||||
sequence_name = table._sequence_name
|
|
||||||
return ['DROP TABLE %s %s;' % (table.sqlsafe, mode), 'DROP GENERATOR %s;' % sequence_name]
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
sql_s = ' FIRST %i SKIP %i %s' % (lmax - lmin, lmin, sql_s)
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def _truncate(self,table,mode = ''):
|
|
||||||
return ['DELETE FROM %s;' % table._tablename,
|
|
||||||
'SET GENERATOR %s TO 0;' % table._sequence_name]
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+?)(\?set_encoding=(?P<charset>\w+))?$')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "firebird"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError("Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
port = int(m.group('port') or 3050)
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
charset = m.group('charset') or 'UTF8'
|
|
||||||
driver_args.update(dsn='%s/%s:%s' % (host,port,db),
|
|
||||||
user = credential_decoder(user),
|
|
||||||
password = credential_decoder(password),
|
|
||||||
charset = charset)
|
|
||||||
|
|
||||||
def connector(driver_args=driver_args):
|
|
||||||
return self.driver.connect(**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def create_sequence_and_triggers(self, query, table, **args):
|
|
||||||
tablename = table._tablename
|
|
||||||
sequence_name = table._sequence_name
|
|
||||||
trigger_name = table._trigger_name
|
|
||||||
self.execute(query)
|
|
||||||
self.execute('create generator %s;' % sequence_name)
|
|
||||||
self.execute('set generator %s to 0;' % sequence_name)
|
|
||||||
self.execute('create trigger %s for %s active before insert position 0 as\nbegin\nif(new.id is null) then\nbegin\nnew.id = gen_id(%s, 1);\nend\nend;' % (trigger_name, tablename, sequence_name))
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
sequence_name = table._sequence_name
|
|
||||||
self.execute('SELECT gen_id(%s, 0) FROM rdb$database' % sequence_name)
|
|
||||||
return long(self.cursor.fetchone()[0])
|
|
||||||
|
|
||||||
|
|
||||||
class FireBirdEmbeddedAdapter(FireBirdAdapter):
|
|
||||||
drivers = ('kinterbasdb','firebirdsql','fdb','pyodbc')
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<path>[^\?]+)(\?set_encoding=(?P<charset>\w+))?$')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "firebird"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
"Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
pathdb = m.group('path')
|
|
||||||
if not pathdb:
|
|
||||||
raise SyntaxError('Path required')
|
|
||||||
charset = m.group('charset')
|
|
||||||
if not charset:
|
|
||||||
charset = 'UTF8'
|
|
||||||
host = ''
|
|
||||||
driver_args.update(host=host,
|
|
||||||
database=pathdb,
|
|
||||||
user=credential_decoder(user),
|
|
||||||
password=credential_decoder(password),
|
|
||||||
charset=charset)
|
|
||||||
|
|
||||||
def connector(driver_args=driver_args):
|
|
||||||
return self.driver.connect(**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
@@ -1,621 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._compat import pjoin
|
|
||||||
from .._globals import IDENTITY, LOGGER, THREAD_LOCAL
|
|
||||||
from .._load import classobj, gae, ndb, NDBDecimalProperty, GAEDecimalProperty, \
|
|
||||||
namespace_manager, Key, NDBPolyModel, PolyModel, rdbms, have_serializers, \
|
|
||||||
serializers, simplejson
|
|
||||||
from ..objects import Table, Field, Expression, Query
|
|
||||||
from ..helpers.classes import SQLCustomType, SQLALL, Reference, UseDatabaseStoredFile
|
|
||||||
from ..helpers.methods import use_common_filters, xorify
|
|
||||||
from .base import NoSQLAdapter
|
|
||||||
from .mysql import MySQLAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class GoogleSQLAdapter(UseDatabaseStoredFile, MySQLAdapter):
|
|
||||||
uploads_in_blob = True
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<instance>.*)/(?P<db>.*)$')
|
|
||||||
|
|
||||||
def __init__(self, db, uri='google:sql://realm:domain/database',
|
|
||||||
pool_size=0, folder=None, db_codec='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "mysql"
|
|
||||||
self.uri = uri
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
if do_connect: self.find_driver(adapter_args, uri)
|
|
||||||
self.folder = folder or pjoin('$HOME',THREAD_LOCAL.folder.split(
|
|
||||||
os.sep+'applications'+os.sep,1)[1])
|
|
||||||
ruri = uri.split("://")[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError("Invalid URI string in SQLDB: %s" % self.uri)
|
|
||||||
instance = credential_decoder(m.group('instance'))
|
|
||||||
self.dbstring = db = credential_decoder(m.group('db'))
|
|
||||||
driver_args['instance'] = instance
|
|
||||||
if not 'charset' in driver_args:
|
|
||||||
driver_args['charset'] = 'utf8'
|
|
||||||
self.createdb = createdb = adapter_args.get('createdb',True)
|
|
||||||
if not createdb:
|
|
||||||
driver_args['database'] = db
|
|
||||||
def connector(driver_args=driver_args):
|
|
||||||
return rdbms.connect(**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
if self.createdb:
|
|
||||||
# self.execute('DROP DATABASE %s' % self.dbstring)
|
|
||||||
self.execute('CREATE DATABASE IF NOT EXISTS %s' % self.dbstring)
|
|
||||||
self.execute('USE %s' % self.dbstring)
|
|
||||||
self.execute("SET FOREIGN_KEY_CHECKS=1;")
|
|
||||||
self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
|
|
||||||
|
|
||||||
def execute(self, command, *a, **b):
|
|
||||||
return self.log_execute(command.decode('utf8'), *a, **b)
|
|
||||||
|
|
||||||
def find_driver(self,adapter_args,uri=None):
|
|
||||||
self.adapter_args = adapter_args
|
|
||||||
self.driver = "google"
|
|
||||||
|
|
||||||
|
|
||||||
class GAEF(object):
|
|
||||||
def __init__(self,name,op,value,apply):
|
|
||||||
self.name=name=='id' and '__key__' or name
|
|
||||||
self.op=op
|
|
||||||
self.value=value
|
|
||||||
self.apply=apply
|
|
||||||
def __repr__(self):
|
|
||||||
return '(%s %s %s:%s)' % (self.name, self.op, repr(self.value), type(self.value))
|
|
||||||
|
|
||||||
|
|
||||||
class GoogleDatastoreAdapter(NoSQLAdapter):
|
|
||||||
"""
|
|
||||||
NDB:
|
|
||||||
|
|
||||||
You can enable NDB by using adapter_args::
|
|
||||||
|
|
||||||
db = DAL('google:datastore', adapter_args={'ndb_settings':ndb_settings, 'use_ndb':True})
|
|
||||||
|
|
||||||
ndb_settings is optional and can be used for per model caching settings.
|
|
||||||
It must be a dict in this form::
|
|
||||||
|
|
||||||
ndb_settings = {<table_name>:{<variable_name>:<variable_value>}}
|
|
||||||
|
|
||||||
See: https://developers.google.com/appengine/docs/python/ndb/cache
|
|
||||||
"""
|
|
||||||
|
|
||||||
MAX_FETCH_LIMIT = 1000000
|
|
||||||
uploads_in_blob = True
|
|
||||||
types = {}
|
|
||||||
# reconnect is not required for Datastore dbs
|
|
||||||
reconnect = lambda *args, **kwargs: None
|
|
||||||
|
|
||||||
def file_exists(self, filename): pass
|
|
||||||
def file_open(self, filename, mode='rb', lock=True): pass
|
|
||||||
def file_close(self, fileobj): pass
|
|
||||||
|
|
||||||
REGEX_NAMESPACE = re.compile('.*://(?P<namespace>.+)')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.use_ndb = adapter_args.get('use_ndb',uri.startswith('google:datastore+ndb'))
|
|
||||||
if self.use_ndb is True:
|
|
||||||
self.types.update({
|
|
||||||
'boolean': ndb.BooleanProperty,
|
|
||||||
'string': (lambda **kwargs: ndb.StringProperty(**kwargs)),
|
|
||||||
'text': ndb.TextProperty,
|
|
||||||
'json': ndb.TextProperty,
|
|
||||||
'password': ndb.StringProperty,
|
|
||||||
'blob': ndb.BlobProperty,
|
|
||||||
'upload': ndb.StringProperty,
|
|
||||||
'integer': ndb.IntegerProperty,
|
|
||||||
'bigint': ndb.IntegerProperty,
|
|
||||||
'float': ndb.FloatProperty,
|
|
||||||
'double': ndb.FloatProperty,
|
|
||||||
'decimal': NDBDecimalProperty,
|
|
||||||
'date': ndb.DateProperty,
|
|
||||||
'time': ndb.TimeProperty,
|
|
||||||
'datetime': ndb.DateTimeProperty,
|
|
||||||
'id': None,
|
|
||||||
'reference': ndb.IntegerProperty,
|
|
||||||
'list:string': (lambda **kwargs: ndb.StringProperty(repeated=True,default=None, **kwargs)),
|
|
||||||
'list:integer': (lambda **kwargs: ndb.IntegerProperty(repeated=True,default=None, **kwargs)),
|
|
||||||
'list:reference': (lambda **kwargs: ndb.IntegerProperty(repeated=True,default=None, **kwargs)),
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
self.types.update({
|
|
||||||
'boolean': gae.BooleanProperty,
|
|
||||||
'string': (lambda **kwargs: gae.StringProperty(multiline=True, **kwargs)),
|
|
||||||
'text': gae.TextProperty,
|
|
||||||
'json': gae.TextProperty,
|
|
||||||
'password': gae.StringProperty,
|
|
||||||
'blob': gae.BlobProperty,
|
|
||||||
'upload': gae.StringProperty,
|
|
||||||
'integer': gae.IntegerProperty,
|
|
||||||
'bigint': gae.IntegerProperty,
|
|
||||||
'float': gae.FloatProperty,
|
|
||||||
'double': gae.FloatProperty,
|
|
||||||
'decimal': GAEDecimalProperty,
|
|
||||||
'date': gae.DateProperty,
|
|
||||||
'time': gae.TimeProperty,
|
|
||||||
'datetime': gae.DateTimeProperty,
|
|
||||||
'id': None,
|
|
||||||
'reference': gae.IntegerProperty,
|
|
||||||
'list:string': (lambda **kwargs: gae.StringListProperty(default=None, **kwargs)),
|
|
||||||
'list:integer': (lambda **kwargs: gae.ListProperty(int,default=None, **kwargs)),
|
|
||||||
'list:reference': (lambda **kwargs: gae.ListProperty(int,default=None, **kwargs)),
|
|
||||||
})
|
|
||||||
self.db = db
|
|
||||||
self.uri = uri
|
|
||||||
self.dbengine = 'google:datastore'
|
|
||||||
self.folder = folder
|
|
||||||
db['_lastsql'] = ''
|
|
||||||
self.db_codec = 'UTF-8'
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.pool_size = 0
|
|
||||||
match = self.REGEX_NAMESPACE.match(uri)
|
|
||||||
if match:
|
|
||||||
namespace_manager.set_namespace(match.group('namespace'))
|
|
||||||
self.keyfunc = (self.use_ndb and ndb.Key) or Key.from_path
|
|
||||||
|
|
||||||
self.ndb_settings = None
|
|
||||||
if 'ndb_settings' in adapter_args:
|
|
||||||
self.ndb_settings = adapter_args['ndb_settings']
|
|
||||||
|
|
||||||
def parse_id(self, value, field_type):
|
|
||||||
return value
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
if fieldtype == "json":
|
|
||||||
if have_serializers:
|
|
||||||
return serializers.json(obj)
|
|
||||||
elif simplejson:
|
|
||||||
return simplejson.dumps(obj)
|
|
||||||
else:
|
|
||||||
raise Exception("Could not dump json object (missing json library)")
|
|
||||||
else:
|
|
||||||
return NoSQLAdapter.represent(self, obj, fieldtype)
|
|
||||||
|
|
||||||
def create_table(self,table,migrate=True,fake_migrate=False, polymodel=None):
|
|
||||||
myfields = {}
|
|
||||||
for field in table:
|
|
||||||
if isinstance(polymodel,Table) and field.name in polymodel.fields():
|
|
||||||
continue
|
|
||||||
attr = {}
|
|
||||||
if isinstance(field.custom_qualifier, dict):
|
|
||||||
#this is custom properties to add to the GAE field declartion
|
|
||||||
attr = field.custom_qualifier
|
|
||||||
field_type = field.type
|
|
||||||
if isinstance(field_type, SQLCustomType):
|
|
||||||
ftype = self.types[field_type.native or field_type.type](**attr)
|
|
||||||
elif isinstance(field_type, ((self.use_ndb and ndb.Property) or gae.Property)):
|
|
||||||
ftype = field_type
|
|
||||||
elif field_type.startswith('id'):
|
|
||||||
continue
|
|
||||||
elif field_type.startswith('decimal'):
|
|
||||||
precision, scale = field_type[7:].strip('()').split(',')
|
|
||||||
precision = int(precision)
|
|
||||||
scale = int(scale)
|
|
||||||
dec_cls = (self.use_ndb and NDBDecimalProperty) or GAEDecimalProperty
|
|
||||||
ftype = dec_cls(precision, scale, **attr)
|
|
||||||
elif field_type.startswith('reference'):
|
|
||||||
if field.notnull:
|
|
||||||
attr = dict(required=True)
|
|
||||||
ftype = self.types[field_type[:9]](**attr)
|
|
||||||
elif field_type.startswith('list:reference'):
|
|
||||||
if field.notnull:
|
|
||||||
attr['required'] = True
|
|
||||||
ftype = self.types[field_type[:14]](**attr)
|
|
||||||
elif field_type.startswith('list:'):
|
|
||||||
ftype = self.types[field_type](**attr)
|
|
||||||
elif not field_type in self.types\
|
|
||||||
or not self.types[field_type]:
|
|
||||||
raise SyntaxError('Field: unknown field type: %s' % field_type)
|
|
||||||
else:
|
|
||||||
ftype = self.types[field_type](**attr)
|
|
||||||
myfields[field.name] = ftype
|
|
||||||
if not polymodel:
|
|
||||||
model_cls = (self.use_ndb and ndb.Model) or gae.Model
|
|
||||||
table._tableobj = classobj(table._tablename, (model_cls, ), myfields)
|
|
||||||
if self.use_ndb:
|
|
||||||
# Set NDB caching variables
|
|
||||||
if self.ndb_settings and (table._tablename in self.ndb_settings):
|
|
||||||
for k, v in self.ndb_settings.iteritems():
|
|
||||||
setattr(table._tableobj, k, v)
|
|
||||||
elif polymodel==True:
|
|
||||||
pm_cls = (self.use_ndb and NDBPolyModel) or PolyModel
|
|
||||||
table._tableobj = classobj(table._tablename, (pm_cls, ), myfields)
|
|
||||||
elif isinstance(polymodel,Table):
|
|
||||||
table._tableobj = classobj(table._tablename, (polymodel._tableobj, ), myfields)
|
|
||||||
else:
|
|
||||||
raise SyntaxError("polymodel must be None, True, a table or a tablename")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def expand(self,expression,field_type=None):
|
|
||||||
if isinstance(expression,Field):
|
|
||||||
if expression.type in ('text', 'blob', 'json'):
|
|
||||||
raise SyntaxError('AppEngine does not index by: %s' % expression.type)
|
|
||||||
return expression.name
|
|
||||||
elif isinstance(expression, (Expression, Query)):
|
|
||||||
if not expression.second is None:
|
|
||||||
return expression.op(expression.first, expression.second)
|
|
||||||
elif not expression.first is None:
|
|
||||||
return expression.op(expression.first)
|
|
||||||
else:
|
|
||||||
return expression.op()
|
|
||||||
elif field_type:
|
|
||||||
return self.represent(expression,field_type)
|
|
||||||
elif isinstance(expression,(list,tuple)):
|
|
||||||
return ','.join([self.represent(item,field_type) for item in expression])
|
|
||||||
else:
|
|
||||||
return str(expression)
|
|
||||||
|
|
||||||
### TODO from gql.py Expression
|
|
||||||
def AND(self,first,second):
|
|
||||||
a = self.expand(first)
|
|
||||||
b = self.expand(second)
|
|
||||||
if b[0].name=='__key__' and a[0].name!='__key__':
|
|
||||||
return b+a
|
|
||||||
return a+b
|
|
||||||
|
|
||||||
def EQ(self,first,second=None):
|
|
||||||
if isinstance(second, Key):
|
|
||||||
return [GAEF(first.name,'=',second,lambda a,b:a==b)]
|
|
||||||
return [GAEF(first.name,'=',self.represent(second,first.type),lambda a,b:a==b)]
|
|
||||||
|
|
||||||
def NE(self,first,second=None):
|
|
||||||
if first.type != 'id':
|
|
||||||
return [GAEF(first.name,'!=',self.represent(second,first.type),lambda a,b:a!=b)]
|
|
||||||
else:
|
|
||||||
if not second is None:
|
|
||||||
second = self.keyfunc(first._tablename, long(second))
|
|
||||||
return [GAEF(first.name,'!=',second,lambda a,b:a!=b)]
|
|
||||||
|
|
||||||
def LT(self,first,second=None):
|
|
||||||
if first.type != 'id':
|
|
||||||
return [GAEF(first.name,'<',self.represent(second,first.type),lambda a,b:a<b)]
|
|
||||||
else:
|
|
||||||
second = self.keyfunc(first._tablename, long(second))
|
|
||||||
return [GAEF(first.name,'<',second,lambda a,b:a<b)]
|
|
||||||
|
|
||||||
def LE(self,first,second=None):
|
|
||||||
if first.type != 'id':
|
|
||||||
return [GAEF(first.name,'<=',self.represent(second,first.type),lambda a,b:a<=b)]
|
|
||||||
else:
|
|
||||||
second = self.keyfunc(first._tablename, long(second))
|
|
||||||
return [GAEF(first.name,'<=',second,lambda a,b:a<=b)]
|
|
||||||
|
|
||||||
def GT(self,first,second=None):
|
|
||||||
if first.type != 'id' or second==0 or second == '0':
|
|
||||||
return [GAEF(first.name,'>',self.represent(second,first.type),lambda a,b:a>b)]
|
|
||||||
else:
|
|
||||||
second = self.keyfunc(first._tablename, long(second))
|
|
||||||
return [GAEF(first.name,'>',second,lambda a,b:a>b)]
|
|
||||||
|
|
||||||
def GE(self,first,second=None):
|
|
||||||
if first.type != 'id':
|
|
||||||
return [GAEF(first.name,'>=',self.represent(second,first.type),lambda a,b:a>=b)]
|
|
||||||
else:
|
|
||||||
second = self.keyfunc(first._tablename, long(second))
|
|
||||||
return [GAEF(first.name,'>=',second,lambda a,b:a>=b)]
|
|
||||||
|
|
||||||
def INVERT(self,first):
|
|
||||||
return '-%s' % first.name
|
|
||||||
|
|
||||||
def COMMA(self,first,second):
|
|
||||||
return '%s, %s' % (self.expand(first),self.expand(second))
|
|
||||||
|
|
||||||
def BELONGS(self,first,second=None):
|
|
||||||
if not isinstance(second,(list, tuple, set)):
|
|
||||||
raise SyntaxError("Not supported")
|
|
||||||
if not self.use_ndb:
|
|
||||||
if isinstance(second,set):
|
|
||||||
second = list(second)
|
|
||||||
if first.type == 'id':
|
|
||||||
second = [self.keyfunc(first._tablename, int(i)) for i in second]
|
|
||||||
return [GAEF(first.name,'in',second,lambda a,b:a in b)]
|
|
||||||
|
|
||||||
def CONTAINS(self,first,second,case_sensitive=False):
|
|
||||||
# silently ignoring: GAE can only do case sensitive matches!
|
|
||||||
if not first.type.startswith('list:'):
|
|
||||||
raise SyntaxError("Not supported")
|
|
||||||
return [GAEF(first.name,'=',self.expand(second,first.type[5:]),lambda a,b:b in a)]
|
|
||||||
|
|
||||||
def NOT(self,first):
|
|
||||||
nops = { self.EQ: self.NE,
|
|
||||||
self.NE: self.EQ,
|
|
||||||
self.LT: self.GE,
|
|
||||||
self.GT: self.LE,
|
|
||||||
self.LE: self.GT,
|
|
||||||
self.GE: self.LT}
|
|
||||||
if not isinstance(first,Query):
|
|
||||||
raise SyntaxError("Not suported")
|
|
||||||
nop = nops.get(first.op,None)
|
|
||||||
if not nop:
|
|
||||||
raise SyntaxError("Not suported %s" % first.op.__name__)
|
|
||||||
first.op = nop
|
|
||||||
return self.expand(first)
|
|
||||||
|
|
||||||
def truncate(self,table,mode):
|
|
||||||
self.db(self.db._adapter.id_query(table)).delete()
|
|
||||||
|
|
||||||
GAE_FILTER_OPTIONS = {
|
|
||||||
'=': lambda q, t, p, v: q.filter(getattr(t,p) == v),
|
|
||||||
'>': lambda q, t, p, v: q.filter(getattr(t,p) > v),
|
|
||||||
'<': lambda q, t, p, v: q.filter(getattr(t,p) < v),
|
|
||||||
'<=': lambda q, t, p, v: q.filter(getattr(t,p) <= v),
|
|
||||||
'>=': lambda q, t, p, v: q.filter(getattr(t,p) >= v),
|
|
||||||
'!=': lambda q, t, p, v: q.filter(getattr(t,p) != v),
|
|
||||||
'in': lambda q, t, p, v: q.filter(getattr(t,p).IN(v)),
|
|
||||||
}
|
|
||||||
|
|
||||||
def filter(self, query, tableobj, prop, op, value):
|
|
||||||
return self.GAE_FILTER_OPTIONS[op](query, tableobj, prop, value)
|
|
||||||
|
|
||||||
def select_raw(self,query,fields=None,attributes=None,count_only=False):
|
|
||||||
db = self.db
|
|
||||||
fields = fields or []
|
|
||||||
attributes = attributes or {}
|
|
||||||
args_get = attributes.get
|
|
||||||
new_fields = []
|
|
||||||
|
|
||||||
for item in fields:
|
|
||||||
if isinstance(item,SQLALL):
|
|
||||||
new_fields += item._table
|
|
||||||
else:
|
|
||||||
new_fields.append(item)
|
|
||||||
|
|
||||||
fields = new_fields
|
|
||||||
if query:
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
elif fields:
|
|
||||||
tablename = fields[0].tablename
|
|
||||||
query = db._adapter.id_query(fields[0].table)
|
|
||||||
else:
|
|
||||||
raise SyntaxError("Unable to determine a tablename")
|
|
||||||
|
|
||||||
if query:
|
|
||||||
if use_common_filters(query):
|
|
||||||
query = self.common_filter(query,[tablename])
|
|
||||||
|
|
||||||
#tableobj is a GAE/NDB Model class (or subclass)
|
|
||||||
tableobj = db[tablename]._tableobj
|
|
||||||
filters = self.expand(query)
|
|
||||||
|
|
||||||
projection = None
|
|
||||||
if len(db[tablename].fields) == len(fields):
|
|
||||||
#getting all fields, not a projection query
|
|
||||||
projection = None
|
|
||||||
elif args_get('projection') == True:
|
|
||||||
projection = []
|
|
||||||
for f in fields:
|
|
||||||
if f.type in ['text', 'blob', 'json']:
|
|
||||||
raise SyntaxError(
|
|
||||||
"text and blob field types not allowed in projection queries")
|
|
||||||
else:
|
|
||||||
projection.append(f.name)
|
|
||||||
|
|
||||||
elif args_get('filterfields') is True:
|
|
||||||
projection = []
|
|
||||||
for f in fields:
|
|
||||||
projection.append(f.name)
|
|
||||||
|
|
||||||
# real projection's can't include 'id'.
|
|
||||||
# it will be added to the result later
|
|
||||||
query_projection = [
|
|
||||||
p for p in projection if \
|
|
||||||
p != db[tablename]._id.name] if projection and \
|
|
||||||
args_get('projection') == True\
|
|
||||||
else None
|
|
||||||
|
|
||||||
cursor = args_get('reusecursor')
|
|
||||||
cursor = cursor if isinstance(cursor, str) else None
|
|
||||||
if self.use_ndb:
|
|
||||||
qo = ndb.QueryOptions(projection=query_projection, cursor=cursor)
|
|
||||||
items = tableobj.query(default_options=qo)
|
|
||||||
else:
|
|
||||||
items = gae.Query(tableobj, projection=query_projection, cursor=cursor)
|
|
||||||
|
|
||||||
for filter in filters:
|
|
||||||
if (args_get('projection') == True and
|
|
||||||
filter.name in query_projection and
|
|
||||||
filter.op in ('=', '<=', '>=')):
|
|
||||||
raise SyntaxError("projection fields cannot have equality filters")
|
|
||||||
if filter.name=='__key__' and filter.op=='>' and filter.value==0:
|
|
||||||
continue
|
|
||||||
elif filter.name=='__key__' and filter.op=='=':
|
|
||||||
if filter.value==0:
|
|
||||||
items = []
|
|
||||||
elif isinstance(filter.value, (self.use_ndb and ndb.Key) or Key):
|
|
||||||
# key qeuries return a class instance,
|
|
||||||
# can't use projection
|
|
||||||
# extra values will be ignored in post-processing later
|
|
||||||
item = filter.value.get() if self.use_ndb else tableobj.get(filter.value)
|
|
||||||
items = [item] if item else []
|
|
||||||
else:
|
|
||||||
# key qeuries return a class instance,
|
|
||||||
# can't use projection
|
|
||||||
# extra values will be ignored in post-processing later
|
|
||||||
item = tableobj.get_by_id(filter.value)
|
|
||||||
items = [item] if item else []
|
|
||||||
elif isinstance(items,list): # i.e. there is a single record!
|
|
||||||
items = [i for i in items if filter.apply(
|
|
||||||
getattr(item,filter.name),filter.value)]
|
|
||||||
else:
|
|
||||||
if filter.name=='__key__' and filter.op != 'in':
|
|
||||||
items.order(tableobj._key) if self.use_ndb else items.order('__key__')
|
|
||||||
if self.use_ndb:
|
|
||||||
items = self.filter(items, tableobj, filter.name, filter.op, filter.value)
|
|
||||||
else:
|
|
||||||
items = items.filter('%s %s' % (filter.name,filter.op), filter.value)
|
|
||||||
|
|
||||||
if count_only:
|
|
||||||
items = [len(items) if isinstance(items,list) else items.count()]
|
|
||||||
elif not isinstance(items,list):
|
|
||||||
query = items
|
|
||||||
if args_get('left', None):
|
|
||||||
raise SyntaxError('Set: no left join in appengine')
|
|
||||||
if args_get('groupby', None):
|
|
||||||
raise SyntaxError('Set: no groupby in appengine')
|
|
||||||
orderby = args_get('orderby', False)
|
|
||||||
if orderby:
|
|
||||||
### THIS REALLY NEEDS IMPROVEMENT !!!
|
|
||||||
if isinstance(orderby, (list, tuple)):
|
|
||||||
orderby = xorify(orderby)
|
|
||||||
if isinstance(orderby,Expression):
|
|
||||||
orderby = self.expand(orderby)
|
|
||||||
orders = orderby.split(', ')
|
|
||||||
for order in orders:
|
|
||||||
if self.use_ndb:
|
|
||||||
#TODO There must be a better way
|
|
||||||
def make_order(o):
|
|
||||||
s = str(o)
|
|
||||||
desc = s[0] == '-'
|
|
||||||
s = (desc and s[1:]) or s
|
|
||||||
return (desc and -getattr(tableobj, s)) or getattr(tableobj, s)
|
|
||||||
_order = {'-id':-tableobj._key,'id':tableobj._key}.get(order)
|
|
||||||
if _order is None:
|
|
||||||
_order = make_order(order)
|
|
||||||
query = query.order(_order)
|
|
||||||
else:
|
|
||||||
order={'-id':'-__key__','id':'__key__'}.get(order,order)
|
|
||||||
query = query.order(order)
|
|
||||||
|
|
||||||
if args_get('limitby', None):
|
|
||||||
(lmin, lmax) = attributes['limitby']
|
|
||||||
limit, fetch_args = lmax-lmin, {'offset':lmin,'keys_only':True}
|
|
||||||
|
|
||||||
if self.use_ndb:
|
|
||||||
keys, cursor, more = query.fetch_page(limit,**fetch_args)
|
|
||||||
items = ndb.get_multi(keys)
|
|
||||||
else:
|
|
||||||
keys = query.fetch(limit, **fetch_args)
|
|
||||||
items = gae.get(keys)
|
|
||||||
cursor = query.cursor()
|
|
||||||
#cursor is only useful if there was a limit and we didn't return
|
|
||||||
# all results
|
|
||||||
if args_get('reusecursor'):
|
|
||||||
db['_lastcursor'] = cursor
|
|
||||||
else:
|
|
||||||
# if a limit is not specified, always return an iterator
|
|
||||||
rows = query
|
|
||||||
|
|
||||||
return (items, tablename, projection or db[tablename].fields)
|
|
||||||
|
|
||||||
def select(self,query,fields,attributes):
|
|
||||||
"""
|
|
||||||
This is the GAE version of select. Some notes to consider:
|
|
||||||
- db['_lastsql'] is not set because there is not SQL statement string
|
|
||||||
for a GAE query
|
|
||||||
- 'nativeRef' is a magical fieldname used for self references on GAE
|
|
||||||
- optional attribute 'projection' when set to True will trigger
|
|
||||||
use of the GAE projection queries. note that there are rules for
|
|
||||||
what is accepted imposed by GAE: each field must be indexed,
|
|
||||||
projection queries cannot contain blob or text fields, and you
|
|
||||||
cannot use == and also select that same field.
|
|
||||||
see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection
|
|
||||||
- optional attribute 'filterfields' when set to True web2py will only
|
|
||||||
parse the explicitly listed fields into the Rows object, even though
|
|
||||||
all fields are returned in the query. This can be used to reduce
|
|
||||||
memory usage in cases where true projection queries are not
|
|
||||||
usable.
|
|
||||||
- optional attribute 'reusecursor' allows use of cursor with queries
|
|
||||||
that have the limitby attribute. Set the attribute to True for the
|
|
||||||
first query, set it to the value of db['_lastcursor'] to continue
|
|
||||||
a previous query. The user must save the cursor value between
|
|
||||||
requests, and the filters must be identical. It is up to the user
|
|
||||||
to follow google's limitations:
|
|
||||||
https://developers.google.com/appengine/docs/python/datastore/queries#Query_Cursors
|
|
||||||
"""
|
|
||||||
|
|
||||||
(items, tablename, fields) = self.select_raw(query,fields,attributes)
|
|
||||||
# self.db['_lastsql'] = self._select(query,fields,attributes)
|
|
||||||
rows = [[(t==self.db[tablename]._id.name and item) or \
|
|
||||||
(t=='nativeRef' and item) or getattr(item, t) \
|
|
||||||
for t in fields] for item in items]
|
|
||||||
colnames = ['%s.%s' % (tablename, t) for t in fields]
|
|
||||||
processor = attributes.get('processor',self.parse)
|
|
||||||
return processor(rows,fields,colnames,False)
|
|
||||||
|
|
||||||
def parse_list_integers(self, value, field_type):
|
|
||||||
return value[:] if self.use_ndb else value
|
|
||||||
|
|
||||||
def parse_list_strings(self, value, field_type):
|
|
||||||
return value[:] if self.use_ndb else value
|
|
||||||
|
|
||||||
def count(self,query,distinct=None,limit=None):
|
|
||||||
if distinct:
|
|
||||||
raise RuntimeError("COUNT DISTINCT not supported")
|
|
||||||
(items, tablename, fields) = self.select_raw(query,count_only=True)
|
|
||||||
return items[0]
|
|
||||||
|
|
||||||
def delete(self,tablename, query):
|
|
||||||
"""
|
|
||||||
This function was changed on 2010-05-04 because according to
|
|
||||||
http://code.google.com/p/googleappengine/issues/detail?id=3119
|
|
||||||
GAE no longer supports deleting more than 1000 records.
|
|
||||||
"""
|
|
||||||
# self.db['_lastsql'] = self._delete(tablename,query)
|
|
||||||
(items, tablename, fields) = self.select_raw(query)
|
|
||||||
# items can be one item or a query
|
|
||||||
if not isinstance(items,list):
|
|
||||||
#use a keys_only query to ensure that this runs as a datastore
|
|
||||||
# small operations
|
|
||||||
leftitems = items.fetch(1000, keys_only=True)
|
|
||||||
counter = 0
|
|
||||||
while len(leftitems):
|
|
||||||
counter += len(leftitems)
|
|
||||||
if self.use_ndb:
|
|
||||||
ndb.delete_multi(leftitems)
|
|
||||||
else:
|
|
||||||
gae.delete(leftitems)
|
|
||||||
leftitems = items.fetch(1000, keys_only=True)
|
|
||||||
else:
|
|
||||||
counter = len(items)
|
|
||||||
if self.use_ndb:
|
|
||||||
ndb.delete_multi([item.key for item in items])
|
|
||||||
else:
|
|
||||||
gae.delete(items)
|
|
||||||
return counter
|
|
||||||
|
|
||||||
def update(self,tablename,query,update_fields):
|
|
||||||
# self.db['_lastsql'] = self._update(tablename,query,update_fields)
|
|
||||||
(items, tablename, fields) = self.select_raw(query)
|
|
||||||
counter = 0
|
|
||||||
for item in items:
|
|
||||||
for field, value in update_fields:
|
|
||||||
setattr(item, field.name, self.represent(value,field.type))
|
|
||||||
item.put()
|
|
||||||
counter += 1
|
|
||||||
LOGGER.info(str(counter))
|
|
||||||
return counter
|
|
||||||
|
|
||||||
def insert(self,table,fields):
|
|
||||||
dfields=dict((f.name,self.represent(v,f.type)) for f,v in fields)
|
|
||||||
# table._db['_lastsql'] = self._insert(table,fields)
|
|
||||||
tmp = table._tableobj(**dfields)
|
|
||||||
tmp.put()
|
|
||||||
key = tmp.key if self.use_ndb else tmp.key()
|
|
||||||
rid = Reference(key.id())
|
|
||||||
(rid._table, rid._record, rid._gaekey) = (table, None, key)
|
|
||||||
return rid
|
|
||||||
|
|
||||||
def bulk_insert(self,table,items):
|
|
||||||
parsed_items = []
|
|
||||||
for item in items:
|
|
||||||
dfields=dict((f.name,self.represent(v,f.type)) for f,v in item)
|
|
||||||
parsed_items.append(table._tableobj(**dfields))
|
|
||||||
if self.use_ndb:
|
|
||||||
ndb.put_multi(parsed_items)
|
|
||||||
else:
|
|
||||||
gae.put(parsed_items)
|
|
||||||
return True
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,134 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import datetime
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class InformixAdapter(BaseAdapter):
|
|
||||||
drivers = ('informixdb',)
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'BLOB SUB_TYPE 1',
|
|
||||||
'json': 'BLOB SUB_TYPE 1',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BLOB SUB_TYPE 0',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INTEGER',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'DOUBLE PRECISION',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'CHAR(8)',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'SERIAL',
|
|
||||||
'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'BLOB SUB_TYPE 1',
|
|
||||||
'list:string': 'BLOB SUB_TYPE 1',
|
|
||||||
'list:reference': 'BLOB SUB_TYPE 1',
|
|
||||||
'big-id': 'BIGSERIAL',
|
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': 'REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s CONSTRAINT FK_%(table_name)s_%(field_name)s',
|
|
||||||
'reference TFK': 'FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s CONSTRAINT TFK_%(table_name)s_%(field_name)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'Random()'
|
|
||||||
|
|
||||||
def NOT_NULL(self,default,field_type):
|
|
||||||
return 'DEFAULT %s NOT NULL' % self.represent(default,field_type)
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
fetch_amt = lmax - lmin
|
|
||||||
dbms_version = int(self.connection.dbms_version.split('.')[0])
|
|
||||||
if lmin and (dbms_version >= 10):
|
|
||||||
# Requires Informix 10.0+
|
|
||||||
sql_s += ' SKIP %d' % (lmin, )
|
|
||||||
if fetch_amt and (dbms_version >= 9):
|
|
||||||
# Requires Informix 9.0+
|
|
||||||
sql_s += ' FIRST %d' % (fetch_amt, )
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def represent_exceptions(self, obj, fieldtype):
|
|
||||||
if fieldtype == 'date':
|
|
||||||
if isinstance(obj, (datetime.date, datetime.datetime)):
|
|
||||||
obj = obj.isoformat()[:10]
|
|
||||||
else:
|
|
||||||
obj = str(obj)
|
|
||||||
return "to_date('%s','%%Y-%%m-%%d')" % obj
|
|
||||||
elif fieldtype == 'datetime':
|
|
||||||
if isinstance(obj, datetime.datetime):
|
|
||||||
obj = obj.isoformat()[:19].replace('T',' ')
|
|
||||||
elif isinstance(obj, datetime.date):
|
|
||||||
obj = obj.isoformat()[:10]+' 00:00:00'
|
|
||||||
else:
|
|
||||||
obj = str(obj)
|
|
||||||
return "to_date('%s','%%Y-%%m-%%d %%H:%%M:%%S')" % obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "informix"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
"Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
user = credential_decoder(user)
|
|
||||||
password = credential_decoder(password)
|
|
||||||
dsn = '%s@%s' % (db,host)
|
|
||||||
driver_args.update(user=user,password=password,autocommit=True)
|
|
||||||
def connector(dsn=dsn,driver_args=driver_args):
|
|
||||||
return self.driver.connect(dsn,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def execute(self,command):
|
|
||||||
if command[-1:]==';':
|
|
||||||
command = command[:-1]
|
|
||||||
return self.log_execute(command)
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
return self.cursor.sqlerrd[1]
|
|
||||||
|
|
||||||
|
|
||||||
class InformixSEAdapter(InformixAdapter):
|
|
||||||
""" work in progress """
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % \
|
|
||||||
(sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def rowslice(self,rows,minimum=0,maximum=None):
|
|
||||||
if maximum is None:
|
|
||||||
return rows[minimum:]
|
|
||||||
return rows[minimum:maximum]
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .._load import pyodbc
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
# NOTE invalid database object name (ANSI-SQL wants
|
|
||||||
# this form of name to be a delimited identifier)
|
|
||||||
INGRES_SEQNAME='ii***lineitemsequence'
|
|
||||||
|
|
||||||
|
|
||||||
class IngresAdapter(BaseAdapter):
|
|
||||||
drivers = ('pyodbc',)
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'CLOB',
|
|
||||||
'json': 'CLOB',
|
|
||||||
'password': 'VARCHAR(%(length)s)', ## Not sure what this contains utf8 or nvarchar. Or even bytes?
|
|
||||||
'blob': 'BLOB',
|
|
||||||
'upload': 'VARCHAR(%(length)s)', ## FIXME utf8 or nvarchar... or blob? what is this type?
|
|
||||||
'integer': 'INTEGER4', # or int8...
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT8',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'ANSIDATE',
|
|
||||||
'time': 'TIME WITHOUT TIME ZONE',
|
|
||||||
'datetime': 'TIMESTAMP WITHOUT TIME ZONE',
|
|
||||||
'id': 'int not null unique with default next value for %s' % INGRES_SEQNAME,
|
|
||||||
'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'CLOB',
|
|
||||||
'list:string': 'CLOB',
|
|
||||||
'list:reference': 'CLOB',
|
|
||||||
'big-id': 'bigint not null unique with default next value for %s' % INGRES_SEQNAME,
|
|
||||||
'big-reference': 'BIGINT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', ## FIXME TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
def LEFT_JOIN(self):
|
|
||||||
return 'LEFT OUTER JOIN'
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'RANDOM()'
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
fetch_amt = lmax - lmin
|
|
||||||
if fetch_amt:
|
|
||||||
sql_s += ' FIRST %d ' % (fetch_amt, )
|
|
||||||
if lmin:
|
|
||||||
# Requires Ingres 9.2+
|
|
||||||
sql_o += ' OFFSET %d' % (lmin, )
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "ingres"
|
|
||||||
self._driver = pyodbc
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
connstr = uri.split(':', 1)[1]
|
|
||||||
# Simple URI processing
|
|
||||||
connstr = connstr.lstrip()
|
|
||||||
while connstr.startswith('/'):
|
|
||||||
connstr = connstr[1:]
|
|
||||||
if '=' in connstr:
|
|
||||||
# Assume we have a regular ODBC connection string and just use it
|
|
||||||
ruri = connstr
|
|
||||||
else:
|
|
||||||
# Assume only (local) dbname is passed in with OS auth
|
|
||||||
database_name = connstr
|
|
||||||
default_driver_name = 'Ingres'
|
|
||||||
vnode = '(local)'
|
|
||||||
servertype = 'ingres'
|
|
||||||
ruri = 'Driver={%s};Server=%s;Database=%s' % (default_driver_name, vnode, database_name)
|
|
||||||
def connector(cnxn=ruri,driver_args=driver_args):
|
|
||||||
return self.driver.connect(cnxn,**driver_args)
|
|
||||||
|
|
||||||
self.connector = connector
|
|
||||||
|
|
||||||
# TODO if version is >= 10, set types['id'] to Identity column, see http://community.actian.com/wiki/Using_Ingres_Identity_Columns
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def create_sequence_and_triggers(self, query, table, **args):
|
|
||||||
# post create table auto inc code (if needed)
|
|
||||||
# modify table to btree for performance....
|
|
||||||
# Older Ingres releases could use rule/trigger like Oracle above.
|
|
||||||
if hasattr(table,'_primarykey'):
|
|
||||||
modify_tbl_sql = 'modify %s to btree unique on %s' % \
|
|
||||||
(table._tablename,
|
|
||||||
', '.join(["'%s'" % x for x in table.primarykey]))
|
|
||||||
self.execute(modify_tbl_sql)
|
|
||||||
else:
|
|
||||||
tmp_seqname='%s_iisq' % table._tablename
|
|
||||||
query=query.replace(INGRES_SEQNAME, tmp_seqname)
|
|
||||||
self.execute('create sequence %s' % tmp_seqname)
|
|
||||||
self.execute(query)
|
|
||||||
self.execute('modify %s to btree unique on %s' % (table._tablename, 'id'))
|
|
||||||
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
tmp_seqname='%s_iisq' % table
|
|
||||||
self.execute('select current value for %s' % tmp_seqname)
|
|
||||||
return long(self.cursor.fetchone()[0]) # don't really need int type cast here...
|
|
||||||
|
|
||||||
|
|
||||||
class IngresUnicodeAdapter(IngresAdapter):
|
|
||||||
|
|
||||||
drivers = ('pyodbc',)
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'NVARCHAR(%(length)s)',
|
|
||||||
'text': 'NCLOB',
|
|
||||||
'json': 'NCLOB',
|
|
||||||
'password': 'NVARCHAR(%(length)s)', ## Not sure what this contains utf8 or nvarchar. Or even bytes?
|
|
||||||
'blob': 'BLOB',
|
|
||||||
'upload': 'VARCHAR(%(length)s)', ## FIXME utf8 or nvarchar... or blob? what is this type?
|
|
||||||
'integer': 'INTEGER4', # or int8...
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT8',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'ANSIDATE',
|
|
||||||
'time': 'TIME WITHOUT TIME ZONE',
|
|
||||||
'datetime': 'TIMESTAMP WITHOUT TIME ZONE',
|
|
||||||
'id': 'INTEGER4 not null unique with default next value for %s'% INGRES_SEQNAME,
|
|
||||||
'reference': 'INTEGER4, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'NCLOB',
|
|
||||||
'list:string': 'NCLOB',
|
|
||||||
'list:reference': 'NCLOB',
|
|
||||||
'big-id': 'BIGINT not null unique with default next value for %s'% INGRES_SEQNAME,
|
|
||||||
'big-reference': 'BIGINT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', ## FIXME TODO
|
|
||||||
}
|
|
||||||
@@ -1,575 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from ..objects import Table, Query, Field, Expression
|
|
||||||
from ..helpers.classes import SQLALL
|
|
||||||
from ..helpers.methods import xorify
|
|
||||||
from .base import NoSQLAdapter
|
|
||||||
|
|
||||||
class MongoDBAdapter(NoSQLAdapter):
|
|
||||||
drivers = ('pymongo',)
|
|
||||||
driver_auto_json = ['loads','dumps']
|
|
||||||
|
|
||||||
uploads_in_blob = False
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': bool,
|
|
||||||
'string': str,
|
|
||||||
'text': str,
|
|
||||||
'json': str,
|
|
||||||
'password': str,
|
|
||||||
'blob': str,
|
|
||||||
'upload': str,
|
|
||||||
'integer': long,
|
|
||||||
'bigint': long,
|
|
||||||
'float': float,
|
|
||||||
'double': float,
|
|
||||||
'date': datetime.date,
|
|
||||||
'time': datetime.time,
|
|
||||||
'datetime': datetime.datetime,
|
|
||||||
'id': long,
|
|
||||||
'reference': long,
|
|
||||||
'list:string': list,
|
|
||||||
'list:integer': list,
|
|
||||||
'list:reference': list,
|
|
||||||
}
|
|
||||||
|
|
||||||
error_messages = {"javascript_needed": "This must yet be replaced" +
|
|
||||||
" with javascript in order to work."}
|
|
||||||
|
|
||||||
def __init__(self,db,uri='mongodb://127.0.0.1:5984/db',
|
|
||||||
pool_size=0, folder=None, db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
|
|
||||||
self.db = db
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args)
|
|
||||||
import random
|
|
||||||
from bson.objectid import ObjectId
|
|
||||||
from bson.son import SON
|
|
||||||
import pymongo.uri_parser
|
|
||||||
|
|
||||||
m = pymongo.uri_parser.parse_uri(uri)
|
|
||||||
|
|
||||||
self.SON = SON
|
|
||||||
self.ObjectId = ObjectId
|
|
||||||
self.random = random
|
|
||||||
|
|
||||||
self.dbengine = 'mongodb'
|
|
||||||
self.folder = folder
|
|
||||||
db['_lastsql'] = ''
|
|
||||||
self.db_codec = 'UTF-8'
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.pool_size = pool_size
|
|
||||||
#this is the minimum amount of replicates that it should wait
|
|
||||||
# for on insert/update
|
|
||||||
self.minimumreplication = adapter_args.get('minimumreplication',0)
|
|
||||||
# by default all inserts and selects are performand asynchronous,
|
|
||||||
# but now the default is
|
|
||||||
# synchronous, except when overruled by either this default or
|
|
||||||
# function parameter
|
|
||||||
self.safe = adapter_args.get('safe',True)
|
|
||||||
# load user setting for uploads in blob storage
|
|
||||||
self.uploads_in_blob = adapter_args.get('uploads_in_blob', False)
|
|
||||||
|
|
||||||
if isinstance(m,tuple):
|
|
||||||
m = {"database" : m[1]}
|
|
||||||
if m.get('database') is None:
|
|
||||||
raise SyntaxError("Database is required!")
|
|
||||||
|
|
||||||
def connector(uri=self.uri,m=m):
|
|
||||||
# Connection() is deprecated
|
|
||||||
if hasattr(self.driver, "MongoClient"):
|
|
||||||
Connection = self.driver.MongoClient
|
|
||||||
else:
|
|
||||||
Connection = self.driver.Connection
|
|
||||||
return Connection(uri)[m.get('database')]
|
|
||||||
|
|
||||||
self.reconnect(connector,cursor=False)
|
|
||||||
|
|
||||||
def object_id(self, arg=None):
|
|
||||||
""" Convert input to a valid Mongodb ObjectId instance
|
|
||||||
|
|
||||||
self.object_id("<random>") -> ObjectId (not unique) instance """
|
|
||||||
if not arg:
|
|
||||||
arg = 0
|
|
||||||
if isinstance(arg, basestring):
|
|
||||||
# we assume an integer as default input
|
|
||||||
rawhex = len(arg.replace("0x", "").replace("L", "")) == 24
|
|
||||||
if arg.isdigit() and (not rawhex):
|
|
||||||
arg = int(arg)
|
|
||||||
elif arg == "<random>":
|
|
||||||
arg = int("0x%sL" % \
|
|
||||||
"".join([self.random.choice("0123456789abcdef") \
|
|
||||||
for x in range(24)]), 0)
|
|
||||||
elif arg.isalnum():
|
|
||||||
if not arg.startswith("0x"):
|
|
||||||
arg = "0x%s" % arg
|
|
||||||
try:
|
|
||||||
arg = int(arg, 0)
|
|
||||||
except ValueError, e:
|
|
||||||
raise ValueError(
|
|
||||||
"invalid objectid argument string: %s" % e)
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid objectid argument string. " +
|
|
||||||
"Requires an integer or base 16 value")
|
|
||||||
elif isinstance(arg, self.ObjectId):
|
|
||||||
return arg
|
|
||||||
|
|
||||||
if not isinstance(arg, (int, long)):
|
|
||||||
raise TypeError("object_id argument must be of type " +
|
|
||||||
"ObjectId or an objectid representable integer")
|
|
||||||
hexvalue = hex(arg)[2:].rstrip('L').zfill(24)
|
|
||||||
return self.ObjectId(hexvalue)
|
|
||||||
|
|
||||||
def parse_reference(self, value, field_type):
|
|
||||||
# here we have to check for ObjectID before base parse
|
|
||||||
if isinstance(value, self.ObjectId):
|
|
||||||
value = long(str(value), 16)
|
|
||||||
return super(MongoDBAdapter,
|
|
||||||
self).parse_reference(value, field_type)
|
|
||||||
|
|
||||||
def parse_id(self, value, field_type):
|
|
||||||
if isinstance(value, self.ObjectId):
|
|
||||||
value = long(str(value), 16)
|
|
||||||
return super(MongoDBAdapter,
|
|
||||||
self).parse_id(value, field_type)
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
# the base adatpter does not support MongoDB ObjectId
|
|
||||||
if isinstance(obj, self.ObjectId):
|
|
||||||
value = obj
|
|
||||||
else:
|
|
||||||
value = NoSQLAdapter.represent(self, obj, fieldtype)
|
|
||||||
# reference types must be convert to ObjectID
|
|
||||||
if fieldtype =='date':
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
# this piece of data can be stripped off based on the fieldtype
|
|
||||||
t = datetime.time(0, 0, 0)
|
|
||||||
# mongodb doesn't has a date object and so it must datetime,
|
|
||||||
# string or integer
|
|
||||||
return datetime.datetime.combine(value, t)
|
|
||||||
elif fieldtype == 'time':
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
# this piece of data can be stripped of based on the fieldtype
|
|
||||||
d = datetime.date(2000, 1, 1)
|
|
||||||
# mongodb doesn't has a time object and so it must datetime,
|
|
||||||
# string or integer
|
|
||||||
return datetime.datetime.combine(d, value)
|
|
||||||
elif fieldtype == "blob":
|
|
||||||
if value is None:
|
|
||||||
return value
|
|
||||||
from bson import Binary
|
|
||||||
if not isinstance(value, Binary):
|
|
||||||
if not isinstance(value, basestring):
|
|
||||||
return Binary(str(value))
|
|
||||||
return Binary(value)
|
|
||||||
return value
|
|
||||||
elif (isinstance(fieldtype, basestring) and
|
|
||||||
fieldtype.startswith('list:')):
|
|
||||||
if fieldtype.startswith('list:reference'):
|
|
||||||
newval = []
|
|
||||||
for v in value:
|
|
||||||
newval.append(self.object_id(v))
|
|
||||||
return newval
|
|
||||||
return value
|
|
||||||
elif ((isinstance(fieldtype, basestring) and
|
|
||||||
fieldtype.startswith("reference")) or
|
|
||||||
(isinstance(fieldtype, Table)) or fieldtype=="id"):
|
|
||||||
value = self.object_id(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def create_table(self, table, migrate=True, fake_migrate=False,
|
|
||||||
polymodel=None, isCapped=False):
|
|
||||||
if isCapped:
|
|
||||||
raise RuntimeError("Not implemented")
|
|
||||||
|
|
||||||
def count(self, query, distinct=None, snapshot=True):
|
|
||||||
if distinct:
|
|
||||||
raise RuntimeError("COUNT DISTINCT not supported")
|
|
||||||
if not isinstance(query,Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
return long(self.select(query,[self.db[tablename]._id], {},
|
|
||||||
count=True,snapshot=snapshot)['count'])
|
|
||||||
# Maybe it would be faster if we just implemented the pymongo
|
|
||||||
# .count() function which is probably quicker?
|
|
||||||
# therefor call __select() connection[table].find(query).count()
|
|
||||||
# Since this will probably reduce the return set?
|
|
||||||
|
|
||||||
def expand(self, expression, field_type=None):
|
|
||||||
if isinstance(expression, Query):
|
|
||||||
# any query using 'id':=
|
|
||||||
# set name as _id (as per pymongo/mongodb primary key)
|
|
||||||
# convert second arg to an objectid field
|
|
||||||
# (if its not already)
|
|
||||||
# if second arg is 0 convert to objectid
|
|
||||||
if isinstance(expression.first,Field) and \
|
|
||||||
((expression.first.type == 'id') or \
|
|
||||||
("reference" in expression.first.type)):
|
|
||||||
if expression.first.type == 'id':
|
|
||||||
expression.first.name = '_id'
|
|
||||||
# cast to Mongo ObjectId
|
|
||||||
if isinstance(expression.second, (tuple, list, set)):
|
|
||||||
expression.second = [self.object_id(item) for
|
|
||||||
item in expression.second]
|
|
||||||
else:
|
|
||||||
expression.second = self.object_id(expression.second)
|
|
||||||
result = expression.op(expression.first, expression.second)
|
|
||||||
|
|
||||||
if isinstance(expression, Field):
|
|
||||||
if expression.type=='id':
|
|
||||||
result = "_id"
|
|
||||||
else:
|
|
||||||
result = expression.name
|
|
||||||
elif isinstance(expression, (Expression, Query)):
|
|
||||||
if not expression.second is None:
|
|
||||||
result = expression.op(expression.first, expression.second)
|
|
||||||
elif not expression.first is None:
|
|
||||||
result = expression.op(expression.first)
|
|
||||||
elif not isinstance(expression.op, str):
|
|
||||||
result = expression.op()
|
|
||||||
else:
|
|
||||||
result = expression.op
|
|
||||||
elif field_type:
|
|
||||||
result = self.represent(expression,field_type)
|
|
||||||
elif isinstance(expression,(list,tuple)):
|
|
||||||
result = [self.represent(item,field_type) for
|
|
||||||
item in expression]
|
|
||||||
else:
|
|
||||||
result = expression
|
|
||||||
return result
|
|
||||||
|
|
||||||
def drop(self, table, mode=''):
|
|
||||||
ctable = self.connection[table._tablename]
|
|
||||||
ctable.drop()
|
|
||||||
|
|
||||||
def truncate(self, table, mode, safe=None):
|
|
||||||
if safe == None:
|
|
||||||
safe=self.safe
|
|
||||||
ctable = self.connection[table._tablename]
|
|
||||||
ctable.remove(None, safe=True)
|
|
||||||
|
|
||||||
def select(self, query, fields, attributes, count=False,
|
|
||||||
snapshot=False):
|
|
||||||
mongofields_dict = self.SON()
|
|
||||||
mongoqry_dict = {}
|
|
||||||
new_fields, mongosort_list = [], []
|
|
||||||
# try an orderby attribute
|
|
||||||
orderby = attributes.get('orderby', False)
|
|
||||||
limitby = attributes.get('limitby', False)
|
|
||||||
# distinct = attributes.get('distinct', False)
|
|
||||||
if 'for_update' in attributes:
|
|
||||||
logging.warn('mongodb does not support for_update')
|
|
||||||
for key in set(attributes.keys())-set(('limitby',
|
|
||||||
'orderby','for_update')):
|
|
||||||
if attributes[key] is not None:
|
|
||||||
logging.warn('select attribute not implemented: %s' % key)
|
|
||||||
if limitby:
|
|
||||||
limitby_skip, limitby_limit = limitby[0], int(limitby[1])
|
|
||||||
else:
|
|
||||||
limitby_skip = limitby_limit = 0
|
|
||||||
if orderby:
|
|
||||||
if isinstance(orderby, (list, tuple)):
|
|
||||||
orderby = xorify(orderby)
|
|
||||||
# !!!! need to add 'random'
|
|
||||||
for f in self.expand(orderby).split(','):
|
|
||||||
if f.startswith('-'):
|
|
||||||
mongosort_list.append((f[1:], -1))
|
|
||||||
else:
|
|
||||||
mongosort_list.append((f, 1))
|
|
||||||
for item in fields:
|
|
||||||
if isinstance(item, SQLALL):
|
|
||||||
new_fields += item._table
|
|
||||||
else:
|
|
||||||
new_fields.append(item)
|
|
||||||
fields = new_fields
|
|
||||||
if isinstance(query,Query):
|
|
||||||
tablename = self.get_table(query)
|
|
||||||
elif len(fields) != 0:
|
|
||||||
tablename = fields[0].tablename
|
|
||||||
else:
|
|
||||||
raise SyntaxError("The table name could not be found in " +
|
|
||||||
"the query nor from the select statement.")
|
|
||||||
mongoqry_dict = self.expand(query)
|
|
||||||
fields = fields or self.db[tablename]
|
|
||||||
for field in fields:
|
|
||||||
mongofields_dict[field.name] = 1
|
|
||||||
ctable = self.connection[tablename]
|
|
||||||
if count:
|
|
||||||
return {'count' : ctable.find(
|
|
||||||
mongoqry_dict, mongofields_dict,
|
|
||||||
skip=limitby_skip, limit=limitby_limit,
|
|
||||||
sort=mongosort_list, snapshot=snapshot).count()}
|
|
||||||
else:
|
|
||||||
# pymongo cursor object
|
|
||||||
mongo_list_dicts = ctable.find(mongoqry_dict,
|
|
||||||
mongofields_dict, skip=limitby_skip,
|
|
||||||
limit=limitby_limit, sort=mongosort_list,
|
|
||||||
snapshot=snapshot)
|
|
||||||
rows = []
|
|
||||||
# populate row in proper order
|
|
||||||
# Here we replace ._id with .id to follow the standard naming
|
|
||||||
colnames = []
|
|
||||||
newnames = []
|
|
||||||
for field in fields:
|
|
||||||
colname = str(field)
|
|
||||||
colnames.append(colname)
|
|
||||||
tablename, fieldname = colname.split(".")
|
|
||||||
if fieldname == "_id":
|
|
||||||
# Mongodb reserved uuid key
|
|
||||||
field.name = "id"
|
|
||||||
newnames.append(".".join((tablename, field.name)))
|
|
||||||
|
|
||||||
for record in mongo_list_dicts:
|
|
||||||
row=[]
|
|
||||||
for colname in colnames:
|
|
||||||
tablename, fieldname = colname.split(".")
|
|
||||||
# switch to Mongo _id uuids for retrieving
|
|
||||||
# record id's
|
|
||||||
if fieldname == "id": fieldname = "_id"
|
|
||||||
if fieldname in record:
|
|
||||||
value = record[fieldname]
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
row.append(value)
|
|
||||||
rows.append(row)
|
|
||||||
processor = attributes.get('processor', self.parse)
|
|
||||||
result = processor(rows, fields, newnames, False)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def insert(self, table, fields, safe=None):
|
|
||||||
"""Safe determines whether a asynchronous request is done or a
|
|
||||||
synchronous action is done
|
|
||||||
For safety, we use by default synchronous requests"""
|
|
||||||
|
|
||||||
values = dict()
|
|
||||||
if safe is None:
|
|
||||||
safe = self.safe
|
|
||||||
ctable = self.connection[table._tablename]
|
|
||||||
for k, v in fields:
|
|
||||||
if not k.name in ["id", "safe"]:
|
|
||||||
fieldname = k.name
|
|
||||||
fieldtype = table[k.name].type
|
|
||||||
values[fieldname] = self.represent(v, fieldtype)
|
|
||||||
|
|
||||||
ctable.insert(values, safe=safe)
|
|
||||||
return long(str(values['_id']), 16)
|
|
||||||
|
|
||||||
def update(self, tablename, query, fields, safe=None):
|
|
||||||
if safe == None:
|
|
||||||
safe = self.safe
|
|
||||||
# return amount of adjusted rows or zero, but no exceptions
|
|
||||||
# @ related not finding the result
|
|
||||||
if not isinstance(query, Query):
|
|
||||||
raise RuntimeError("Not implemented")
|
|
||||||
amount = self.count(query, False)
|
|
||||||
if not isinstance(query, Query):
|
|
||||||
raise SyntaxError("Not Supported")
|
|
||||||
filter = None
|
|
||||||
if query:
|
|
||||||
filter = self.expand(query)
|
|
||||||
# do not try to update id fields to avoid backend errors
|
|
||||||
modify = {'$set': dict((k.name, self.represent(v, k.type)) for
|
|
||||||
k, v in fields if (not k.name in ("_id", "id")))}
|
|
||||||
try:
|
|
||||||
result = self.connection[tablename].update(filter,
|
|
||||||
modify, multi=True, safe=safe)
|
|
||||||
if safe:
|
|
||||||
try:
|
|
||||||
# if result count is available fetch it
|
|
||||||
return result["n"]
|
|
||||||
except (KeyError, AttributeError, TypeError):
|
|
||||||
return amount
|
|
||||||
else:
|
|
||||||
return amount
|
|
||||||
except Exception, e:
|
|
||||||
# TODO Reverse update query to verifiy that the query succeded
|
|
||||||
raise RuntimeError("uncaught exception when updating rows: %s" % e)
|
|
||||||
|
|
||||||
def delete(self, tablename, query, safe=None):
|
|
||||||
if safe is None:
|
|
||||||
safe = self.safe
|
|
||||||
amount = 0
|
|
||||||
amount = self.count(query, False)
|
|
||||||
if not isinstance(query, Query):
|
|
||||||
raise RuntimeError("query type %s is not supported" % \
|
|
||||||
type(query))
|
|
||||||
filter = self.expand(query)
|
|
||||||
self.connection[tablename].remove(filter, safe=safe)
|
|
||||||
return amount
|
|
||||||
|
|
||||||
def bulk_insert(self, table, items):
|
|
||||||
return [self.insert(table,item) for item in items]
|
|
||||||
|
|
||||||
## OPERATORS
|
|
||||||
def INVERT(self, first):
|
|
||||||
#print "in invert first=%s" % first
|
|
||||||
return '-%s' % self.expand(first)
|
|
||||||
|
|
||||||
# TODO This will probably not work:(
|
|
||||||
def NOT(self, first):
|
|
||||||
return {'$not': self.expand(first)}
|
|
||||||
|
|
||||||
def AND(self,first,second):
|
|
||||||
# pymongo expects: .find({'$and': [{'x':'1'}, {'y':'2'}]})
|
|
||||||
return {'$and': [self.expand(first),self.expand(second)]}
|
|
||||||
|
|
||||||
def OR(self,first,second):
|
|
||||||
# pymongo expects: .find({'$or': [{'name':'1'}, {'name':'2'}]})
|
|
||||||
return {'$or': [self.expand(first),self.expand(second)]}
|
|
||||||
|
|
||||||
def BELONGS(self, first, second):
|
|
||||||
if isinstance(second, str):
|
|
||||||
return {self.expand(first) : {"$in" : [ second[:-1]]} }
|
|
||||||
elif second==[] or second==() or second==set():
|
|
||||||
return {1:0}
|
|
||||||
items = [self.expand(item, first.type) for item in second]
|
|
||||||
return {self.expand(first) : {"$in" : items} }
|
|
||||||
|
|
||||||
def EQ(self,first,second=None):
|
|
||||||
result = {}
|
|
||||||
result[self.expand(first)] = self.expand(second)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def NE(self, first, second=None):
|
|
||||||
result = {}
|
|
||||||
result[self.expand(first)] = {'$ne': self.expand(second)}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def LT(self,first,second=None):
|
|
||||||
if second is None:
|
|
||||||
raise RuntimeError("Cannot compare %s < None" % first)
|
|
||||||
result = {}
|
|
||||||
result[self.expand(first)] = {'$lt': self.expand(second)}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def LE(self,first,second=None):
|
|
||||||
if second is None:
|
|
||||||
raise RuntimeError("Cannot compare %s <= None" % first)
|
|
||||||
result = {}
|
|
||||||
result[self.expand(first)] = {'$lte': self.expand(second)}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def GT(self,first,second):
|
|
||||||
result = {}
|
|
||||||
result[self.expand(first)] = {'$gt': self.expand(second)}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def GE(self,first,second=None):
|
|
||||||
if second is None:
|
|
||||||
raise RuntimeError("Cannot compare %s >= None" % first)
|
|
||||||
result = {}
|
|
||||||
result[self.expand(first)] = {'$gte': self.expand(second)}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def ADD(self, first, second):
|
|
||||||
raise NotImplementedError(self.error_messages["javascript_needed"])
|
|
||||||
return '%s + %s' % (self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def SUB(self, first, second):
|
|
||||||
raise NotImplementedError(self.error_messages["javascript_needed"])
|
|
||||||
return '(%s - %s)' % (self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def MUL(self, first, second):
|
|
||||||
raise NotImplementedError(self.error_messages["javascript_needed"])
|
|
||||||
return '(%s * %s)' % (self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def DIV(self, first, second):
|
|
||||||
raise NotImplementedError(self.error_messages["javascript_needed"])
|
|
||||||
return '(%s / %s)' % (self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def MOD(self, first, second):
|
|
||||||
raise NotImplementedError(self.error_messages["javascript_needed"])
|
|
||||||
return '(%s %% %s)' % (self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def AS(self, first, second):
|
|
||||||
raise NotImplementedError(self.error_messages["javascript_needed"])
|
|
||||||
return '%s AS %s' % (self.expand(first), second)
|
|
||||||
|
|
||||||
# We could implement an option that simulates a full featured SQL
|
|
||||||
# database. But I think the option should be set explicit or
|
|
||||||
# implemented as another library.
|
|
||||||
def ON(self, first, second):
|
|
||||||
raise NotImplementedError("This is not possible in NoSQL" +
|
|
||||||
" but can be simulated with a wrapper.")
|
|
||||||
return '%s ON %s' % (self.expand(first), self.expand(second))
|
|
||||||
|
|
||||||
# BLOW ARE TWO IMPLEMENTATIONS OF THE SAME FUNCITONS
|
|
||||||
# WHICH ONE IS BEST?
|
|
||||||
|
|
||||||
def COMMA(self, first, second):
|
|
||||||
return '%s, %s' % (self.expand(first), self.expand(second))
|
|
||||||
|
|
||||||
def LIKE(self, first, second):
|
|
||||||
#escaping regex operators?
|
|
||||||
return {self.expand(first): ('%s' % \
|
|
||||||
self.expand(second, 'string').replace('%','/'))}
|
|
||||||
|
|
||||||
def ILIKE(self, first, second):
|
|
||||||
val = second if isinstance(second,self.ObjectId) else {
|
|
||||||
'$regex': second.replace('%', ''), '$options': 'i'}
|
|
||||||
return {self.expand(first): val}
|
|
||||||
|
|
||||||
def STARTSWITH(self, first, second):
|
|
||||||
#escaping regex operators?
|
|
||||||
return {self.expand(first): ('/^%s/' % \
|
|
||||||
self.expand(second, 'string'))}
|
|
||||||
|
|
||||||
def ENDSWITH(self, first, second):
|
|
||||||
#escaping regex operators?
|
|
||||||
return {self.expand(first): ('/%s^/' % \
|
|
||||||
self.expand(second, 'string'))}
|
|
||||||
|
|
||||||
def CONTAINS(self, first, second, case_sensitive=False):
|
|
||||||
# silently ignore, only case sensitive
|
|
||||||
# There is a technical difference, but mongodb doesn't support
|
|
||||||
# that, but the result will be the same
|
|
||||||
val = second if isinstance(second,self.ObjectId) else \
|
|
||||||
{'$regex':".*" + re.escape(self.expand(second, 'string')) + ".*"}
|
|
||||||
return {self.expand(first) : val}
|
|
||||||
|
|
||||||
def LIKE(self, first, second):
|
|
||||||
import re
|
|
||||||
return {self.expand(first): {'$regex': \
|
|
||||||
re.escape(self.expand(second,
|
|
||||||
'string')).replace('%','.*')}}
|
|
||||||
|
|
||||||
#TODO verify full compatibilty with official SQL Like operator
|
|
||||||
def STARTSWITH(self, first, second):
|
|
||||||
#TODO Solve almost the same problem as with endswith
|
|
||||||
import re
|
|
||||||
return {self.expand(first): {'$regex' : '^' +
|
|
||||||
re.escape(self.expand(second,
|
|
||||||
'string'))}}
|
|
||||||
|
|
||||||
#TODO verify full compatibilty with official SQL Like operator
|
|
||||||
def ENDSWITH(self, first, second):
|
|
||||||
#escaping regex operators?
|
|
||||||
#TODO if searched for a name like zsa_corbitt and the function
|
|
||||||
# is endswith('a') then this is also returned.
|
|
||||||
# Aldo it end with a t
|
|
||||||
import re
|
|
||||||
return {self.expand(first): {'$regex': \
|
|
||||||
re.escape(self.expand(second, 'string')) + '$'}}
|
|
||||||
|
|
||||||
#TODO verify full compatibilty with official oracle contains operator
|
|
||||||
def CONTAINS(self, first, second, case_sensitive=False):
|
|
||||||
# silently ignore, only case sensitive
|
|
||||||
#There is a technical difference, but mongodb doesn't support
|
|
||||||
# that, but the result will be the same
|
|
||||||
#TODO contains operators need to be transformed to Regex
|
|
||||||
return {self.expand(first) : {'$regex': \
|
|
||||||
".*" + re.escape(self.expand(second, 'string')) + ".*"}}
|
|
||||||
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .._globals import IDENTITY, LOGGER
|
|
||||||
from ..helpers.methods import varquote_aux
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class MSSQLAdapter(BaseAdapter):
|
|
||||||
drivers = ('pyodbc',)
|
|
||||||
T_SEP = 'T'
|
|
||||||
|
|
||||||
QUOTE_TEMPLATE = '"%s"'
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'BIT',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'TEXT',
|
|
||||||
'json': 'TEXT',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'IMAGE',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATETIME',
|
|
||||||
'time': 'CHAR(8)',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'INT IDENTITY PRIMARY KEY',
|
|
||||||
'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'TEXT',
|
|
||||||
'list:string': 'TEXT',
|
|
||||||
'list:reference': 'TEXT',
|
|
||||||
'geometry': 'geometry',
|
|
||||||
'geography': 'geography',
|
|
||||||
'big-id': 'BIGINT IDENTITY PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def concat_add(self,tablename):
|
|
||||||
return '; ALTER TABLE %s ADD ' % tablename
|
|
||||||
|
|
||||||
def varquote(self,name):
|
|
||||||
return varquote_aux(name,'[%s]')
|
|
||||||
|
|
||||||
def EXTRACT(self,field,what):
|
|
||||||
return "DATEPART(%s,%s)" % (what, self.expand(field))
|
|
||||||
|
|
||||||
def LEFT_JOIN(self):
|
|
||||||
return 'LEFT OUTER JOIN'
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'NEWID()'
|
|
||||||
|
|
||||||
def ALLOW_NULL(self):
|
|
||||||
return ' NULL'
|
|
||||||
|
|
||||||
def CAST(self, first, second):
|
|
||||||
return first # apparently no cast necessary in MSSQL
|
|
||||||
|
|
||||||
def SUBSTRING(self,field,parameters):
|
|
||||||
return 'SUBSTRING(%s,%s,%s)' % (self.expand(field), parameters[0], parameters[1])
|
|
||||||
|
|
||||||
def PRIMARY_KEY(self,key):
|
|
||||||
return 'PRIMARY KEY CLUSTERED (%s)' % key
|
|
||||||
|
|
||||||
def AGGREGATE(self, first, what):
|
|
||||||
if what == 'LENGTH':
|
|
||||||
what = 'LEN'
|
|
||||||
return "%s(%s)" % (what, self.expand(first))
|
|
||||||
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
sql_s += ' TOP %i' % lmax
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
TRUE = 1
|
|
||||||
FALSE = 0
|
|
||||||
|
|
||||||
REGEX_DSN = re.compile('^(?P<dsn>.+)$')
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?(?P<urlargs>.*))?$')
|
|
||||||
REGEX_ARGPATTERN = re.compile('(?P<argkey>[^=]+)=(?P<argvalue>[^&]*)')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, srid=4326,
|
|
||||||
after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "mssql"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.srid = srid
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
# ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
if '@' not in ruri:
|
|
||||||
try:
|
|
||||||
m = self.REGEX_DSN.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
'Parsing uri string(%s) has no result' % self.uri)
|
|
||||||
dsn = m.group('dsn')
|
|
||||||
if not dsn:
|
|
||||||
raise SyntaxError('DSN required')
|
|
||||||
except SyntaxError:
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
LOGGER.error('NdGpatch error')
|
|
||||||
raise e
|
|
||||||
# was cnxn = 'DSN=%s' % dsn
|
|
||||||
cnxn = dsn
|
|
||||||
else:
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
"Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
port = m.group('port') or '1433'
|
|
||||||
# Parse the optional url name-value arg pairs after the '?'
|
|
||||||
# (in the form of arg1=value1&arg2=value2&...)
|
|
||||||
# Default values (drivers like FreeTDS insist on uppercase parameter keys)
|
|
||||||
argsdict = { 'DRIVER':'{SQL Server}' }
|
|
||||||
urlargs = m.group('urlargs') or ''
|
|
||||||
for argmatch in self.REGEX_ARGPATTERN.finditer(urlargs):
|
|
||||||
argsdict[str(argmatch.group('argkey')).upper()] = argmatch.group('argvalue')
|
|
||||||
urlargs = ';'.join(['%s=%s' % (ak, av) for (ak, av) in argsdict.iteritems()])
|
|
||||||
cnxn = 'SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;%s' \
|
|
||||||
% (host, port, db, user, password, urlargs)
|
|
||||||
def connector(cnxn=cnxn,driver_args=driver_args):
|
|
||||||
return self.driver.connect(cnxn,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
#self.execute('SELECT @@IDENTITY;')
|
|
||||||
self.execute('SELECT SCOPE_IDENTITY();')
|
|
||||||
return long(self.cursor.fetchone()[0])
|
|
||||||
|
|
||||||
def rowslice(self,rows,minimum=0,maximum=None):
|
|
||||||
if maximum is None:
|
|
||||||
return rows[minimum:]
|
|
||||||
return rows[minimum:maximum]
|
|
||||||
|
|
||||||
def EPOCH(self, first):
|
|
||||||
return "DATEDIFF(second, '1970-01-01 00:00:00', %s)" % self.expand(first)
|
|
||||||
|
|
||||||
def CONCAT(self, *items):
|
|
||||||
return '(%s)' % ' + '.join(self.expand(x,'string') for x in items)
|
|
||||||
|
|
||||||
# GIS Spatial Extensions
|
|
||||||
|
|
||||||
# No STAsGeoJSON in MSSQL
|
|
||||||
|
|
||||||
def ST_ASTEXT(self, first):
|
|
||||||
return '%s.STAsText()' %(self.expand(first))
|
|
||||||
|
|
||||||
def ST_CONTAINS(self, first, second):
|
|
||||||
return '%s.STContains(%s)=1' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_DISTANCE(self, first, second):
|
|
||||||
return '%s.STDistance(%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_EQUALS(self, first, second):
|
|
||||||
return '%s.STEquals(%s)=1' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_INTERSECTS(self, first, second):
|
|
||||||
return '%s.STIntersects(%s)=1' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_OVERLAPS(self, first, second):
|
|
||||||
return '%s.STOverlaps(%s)=1' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
# no STSimplify in MSSQL
|
|
||||||
|
|
||||||
def ST_TOUCHES(self, first, second):
|
|
||||||
return '%s.STTouches(%s)=1' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_WITHIN(self, first, second):
|
|
||||||
return '%s.STWithin(%s)=1' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
field_is_type = fieldtype.startswith
|
|
||||||
if field_is_type('geometry'):
|
|
||||||
srid = 0 # MS SQL default srid for geometry
|
|
||||||
geotype, parms = fieldtype[:-1].split('(')
|
|
||||||
if parms:
|
|
||||||
srid = parms
|
|
||||||
return "geometry::STGeomFromText('%s',%s)" %(obj, srid)
|
|
||||||
elif fieldtype == 'geography':
|
|
||||||
srid = 4326 # MS SQL default srid for geography
|
|
||||||
geotype, parms = fieldtype[:-1].split('(')
|
|
||||||
if parms:
|
|
||||||
srid = parms
|
|
||||||
return "geography::STGeomFromText('%s',%s)" %(obj, srid)
|
|
||||||
# else:
|
|
||||||
# raise SyntaxError('Invalid field type %s' %fieldtype)
|
|
||||||
return "geometry::STGeomFromText('%s',%s)" %(obj, srid)
|
|
||||||
return BaseAdapter.represent(self, obj, fieldtype)
|
|
||||||
|
|
||||||
|
|
||||||
class MSSQL3Adapter(MSSQLAdapter):
|
|
||||||
"""Experimental support for pagination in MSSQL
|
|
||||||
|
|
||||||
Requires MSSQL >= 2005, uses `ROW_NUMBER()`
|
|
||||||
"""
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'BIT',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'VARCHAR(MAX)',
|
|
||||||
'json': 'VARCHAR(MAX)',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'IMAGE',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATETIME',
|
|
||||||
'time': 'TIME(7)',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'INT IDENTITY PRIMARY KEY',
|
|
||||||
'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'VARCHAR(MAX)',
|
|
||||||
'list:string': 'VARCHAR(MAX)',
|
|
||||||
'list:reference': 'VARCHAR(MAX)',
|
|
||||||
'geometry': 'geometry',
|
|
||||||
'geography': 'geography',
|
|
||||||
'big-id': 'BIGINT IDENTITY PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
if lmin == 0:
|
|
||||||
sql_s += ' TOP %i' % lmax
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
lmin += 1
|
|
||||||
sql_o_inner = sql_o[sql_o.find('ORDER BY ')+9:]
|
|
||||||
sql_g_inner = sql_o[:sql_o.find('ORDER BY ')]
|
|
||||||
sql_f_outer = ['f_%s' % f for f in range(len(sql_f.split(',')))]
|
|
||||||
sql_f_inner = [f for f in sql_f.split(',')]
|
|
||||||
sql_f_iproxy = ['%s AS %s' % (o, n) for (o, n) in zip(sql_f_inner, sql_f_outer)]
|
|
||||||
sql_f_iproxy = ', '.join(sql_f_iproxy)
|
|
||||||
sql_f_oproxy = ', '.join(sql_f_outer)
|
|
||||||
return 'SELECT %s %s FROM (SELECT %s ROW_NUMBER() OVER (ORDER BY %s) AS w_row, %s FROM %s%s%s) TMP WHERE w_row BETWEEN %i AND %s;' % (sql_s,sql_f_oproxy,sql_s,sql_f,sql_f_iproxy,sql_t,sql_w,sql_g_inner,lmin,lmax)
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s,sql_f,sql_t,sql_w,sql_o)
|
|
||||||
def rowslice(self,rows,minimum=0,maximum=None):
|
|
||||||
return rows
|
|
||||||
|
|
||||||
|
|
||||||
class MSSQL4Adapter(MSSQLAdapter):
|
|
||||||
"""Support for "native" pagination
|
|
||||||
|
|
||||||
Requires MSSQL >= 2012, uses `OFFSET ... ROWS ... FETCH NEXT ... ROWS ONLY`
|
|
||||||
"""
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'BIT',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'VARCHAR(MAX)',
|
|
||||||
'json': 'VARCHAR(MAX)',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'IMAGE',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATETIME',
|
|
||||||
'time': 'TIME(7)',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'INT IDENTITY PRIMARY KEY',
|
|
||||||
'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'VARCHAR(MAX)',
|
|
||||||
'list:string': 'VARCHAR(MAX)',
|
|
||||||
'list:reference': 'VARCHAR(MAX)',
|
|
||||||
'geometry': 'geometry',
|
|
||||||
'geography': 'geography',
|
|
||||||
'big-id': 'BIGINT IDENTITY PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
if lmin == 0:
|
|
||||||
#top is still slightly faster, especially because
|
|
||||||
#web2py's default to fetch references is to not specify
|
|
||||||
#an orderby clause
|
|
||||||
sql_s += ' TOP %i' % lmax
|
|
||||||
else:
|
|
||||||
if not sql_o:
|
|
||||||
#if there is no orderby, we can't use the brand new statements
|
|
||||||
#that being said, developer chose its own poison, so be it random
|
|
||||||
sql_o += ' ORDER BY %s' % self.RANDOM()
|
|
||||||
sql_o += ' OFFSET %i ROWS FETCH NEXT %i ROWS ONLY' % (lmin, lmax - lmin)
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % \
|
|
||||||
(sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def rowslice(self,rows,minimum=0,maximum=None):
|
|
||||||
return rows
|
|
||||||
|
|
||||||
|
|
||||||
class MSSQL2Adapter(MSSQLAdapter):
|
|
||||||
drivers = ('pyodbc',)
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'NVARCHAR(%(length)s)',
|
|
||||||
'text': 'NTEXT',
|
|
||||||
'json': 'NTEXT',
|
|
||||||
'password': 'NVARCHAR(%(length)s)',
|
|
||||||
'blob': 'IMAGE',
|
|
||||||
'upload': 'NVARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATETIME',
|
|
||||||
'time': 'CHAR(8)',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'INT IDENTITY PRIMARY KEY',
|
|
||||||
'reference': 'INT, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'NTEXT',
|
|
||||||
'list:string': 'NTEXT',
|
|
||||||
'list:reference': 'NTEXT',
|
|
||||||
'big-id': 'BIGINT IDENTITY PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
value = BaseAdapter.represent(self, obj, fieldtype)
|
|
||||||
if fieldtype in ('string','text', 'json') and value[:1]=="'":
|
|
||||||
value = 'N'+value
|
|
||||||
return value
|
|
||||||
|
|
||||||
def execute(self,a):
|
|
||||||
return self.log_execute(a.decode('utf8'))
|
|
||||||
|
|
||||||
|
|
||||||
class VerticaAdapter(MSSQLAdapter):
|
|
||||||
drivers = ('pyodbc',)
|
|
||||||
T_SEP = ' '
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'BOOLEAN',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'BYTEA',
|
|
||||||
'json': 'VARCHAR(%(length)s)',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BYTEA',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'DOUBLE PRECISION',
|
|
||||||
'decimal': 'DECIMAL(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'IDENTITY',
|
|
||||||
'reference': 'INT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'BYTEA',
|
|
||||||
'list:string': 'BYTEA',
|
|
||||||
'list:reference': 'BYTEA',
|
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def EXTRACT(self, first, what):
|
|
||||||
return "DATE_PART('%s', TIMESTAMP %s)" % (what, self.expand(first))
|
|
||||||
|
|
||||||
def _truncate(self, table, mode=''):
|
|
||||||
tablename = table._tablename
|
|
||||||
return ['TRUNCATE %s %s;' % (tablename, mode or '')]
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin)
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % \
|
|
||||||
(sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
self.execute('SELECT LAST_INSERT_ID();')
|
|
||||||
return long(self.cursor.fetchone()[0])
|
|
||||||
|
|
||||||
def execute(self, a):
|
|
||||||
return self.log_execute(a)
|
|
||||||
|
|
||||||
|
|
||||||
class SybaseAdapter(MSSQLAdapter):
|
|
||||||
drivers = ('Sybase',)
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'BIT',
|
|
||||||
'string': 'CHAR VARYING(%(length)s)',
|
|
||||||
'text': 'TEXT',
|
|
||||||
'json': 'TEXT',
|
|
||||||
'password': 'CHAR VARYING(%(length)s)',
|
|
||||||
'blob': 'IMAGE',
|
|
||||||
'upload': 'CHAR VARYING(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATETIME',
|
|
||||||
'time': 'CHAR(8)',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'INT IDENTITY PRIMARY KEY',
|
|
||||||
'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'TEXT',
|
|
||||||
'list:string': 'TEXT',
|
|
||||||
'list:reference': 'TEXT',
|
|
||||||
'geometry': 'geometry',
|
|
||||||
'geography': 'geography',
|
|
||||||
'big-id': 'BIGINT IDENTITY PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, srid=4326,
|
|
||||||
after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "sybase"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.srid = srid
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
# ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
if '@' not in ruri:
|
|
||||||
try:
|
|
||||||
m = self.REGEX_DSN.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
'Parsing uri string(%s) has no result' % self.uri)
|
|
||||||
dsn = m.group('dsn')
|
|
||||||
if not dsn:
|
|
||||||
raise SyntaxError('DSN required')
|
|
||||||
except SyntaxError:
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
LOGGER.error('NdGpatch error')
|
|
||||||
raise e
|
|
||||||
else:
|
|
||||||
m = self.REGEX_URI.match(uri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
"Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
port = m.group('port') or '1433'
|
|
||||||
|
|
||||||
dsn = 'sybase:host=%s:%s;dbname=%s' % (host,port,db)
|
|
||||||
|
|
||||||
driver_args.update(user = credential_decoder(user),
|
|
||||||
password = credential_decoder(password))
|
|
||||||
|
|
||||||
def connector(dsn=dsn,driver_args=driver_args):
|
|
||||||
return self.driver.connect(dsn,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from ..helpers.methods import varquote_aux
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class MySQLAdapter(BaseAdapter):
|
|
||||||
drivers = ('MySQLdb','pymysql', 'mysqlconnector')
|
|
||||||
|
|
||||||
commit_on_alter_table = True
|
|
||||||
support_distributed_transaction = True
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'LONGTEXT',
|
|
||||||
'json': 'LONGTEXT',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'LONGBLOB',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'DOUBLE',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'DATETIME',
|
|
||||||
'id': 'INT AUTO_INCREMENT NOT NULL',
|
|
||||||
'reference': 'INT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'LONGTEXT',
|
|
||||||
'list:string': 'LONGTEXT',
|
|
||||||
'list:reference': 'LONGTEXT',
|
|
||||||
'big-id': 'BIGINT AUTO_INCREMENT NOT NULL',
|
|
||||||
'big-reference': 'BIGINT, INDEX %(index_name)s (%(field_name)s), FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT `FK_%(constraint_name)s` FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
QUOTE_TEMPLATE = "`%s`"
|
|
||||||
|
|
||||||
def varquote(self,name):
|
|
||||||
return varquote_aux(name,'`%s`')
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'RAND()'
|
|
||||||
|
|
||||||
def SUBSTRING(self,field,parameters):
|
|
||||||
return 'SUBSTRING(%s,%s,%s)' % (self.expand(field),
|
|
||||||
parameters[0], parameters[1])
|
|
||||||
|
|
||||||
def EPOCH(self, first):
|
|
||||||
return "UNIX_TIMESTAMP(%s)" % self.expand(first)
|
|
||||||
|
|
||||||
def CONCAT(self, *items):
|
|
||||||
return 'CONCAT(%s)' % ','.join(self.expand(x,'string') for x in items)
|
|
||||||
|
|
||||||
def REGEXP(self,first,second):
|
|
||||||
return '(%s REGEXP %s)' % (self.expand(first),
|
|
||||||
self.expand(second,'string'))
|
|
||||||
|
|
||||||
def CAST(self, first, second):
|
|
||||||
if second=='LONGTEXT': second = 'CHAR'
|
|
||||||
return 'CAST(%s AS %s)' % (first, second)
|
|
||||||
|
|
||||||
def _drop(self,table,mode):
|
|
||||||
# breaks db integrity but without this mysql does not drop table
|
|
||||||
table_rname = table.sqlsafe
|
|
||||||
return ['SET FOREIGN_KEY_CHECKS=0;','DROP TABLE %s;' % table_rname,
|
|
||||||
'SET FOREIGN_KEY_CHECKS=1;']
|
|
||||||
|
|
||||||
def _insert_empty(self, table):
|
|
||||||
return 'INSERT INTO %s VALUES (DEFAULT);' % (table.sqlsafe)
|
|
||||||
|
|
||||||
def distributed_transaction_begin(self,key):
|
|
||||||
self.execute('XA START;')
|
|
||||||
|
|
||||||
def prepare(self,key):
|
|
||||||
self.execute("XA END;")
|
|
||||||
self.execute("XA PREPARE;")
|
|
||||||
|
|
||||||
def commit_prepared(self,key):
|
|
||||||
self.execute("XA COMMIT;")
|
|
||||||
|
|
||||||
def rollback_prepared(self,key):
|
|
||||||
self.execute("XA ROLLBACK;")
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^?]+)(\?set_encoding=(?P<charset>\w+))?$')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "mysql"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError(
|
|
||||||
"Invalid URI string in DAL: %s" % self.uri)
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
port = int(m.group('port') or '3306')
|
|
||||||
charset = m.group('charset') or 'utf8'
|
|
||||||
driver_args.update(db=db,
|
|
||||||
user=credential_decoder(user),
|
|
||||||
passwd=credential_decoder(password),
|
|
||||||
host=host,
|
|
||||||
port=port,
|
|
||||||
charset=charset)
|
|
||||||
|
|
||||||
|
|
||||||
def connector(driver_args=driver_args):
|
|
||||||
return self.driver.connect(**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.execute('SET FOREIGN_KEY_CHECKS=1;')
|
|
||||||
self.execute("SET sql_mode='NO_BACKSLASH_ESCAPES';")
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
self.execute('select last_insert_id();')
|
|
||||||
return int(self.cursor.fetchone()[0])
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import base64
|
|
||||||
import datetime
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .._load import cx_Oracle
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
class OracleAdapter(BaseAdapter):
|
|
||||||
drivers = ('cx_Oracle',)
|
|
||||||
|
|
||||||
commit_on_alter_table = False
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR2(%(length)s)',
|
|
||||||
'text': 'CLOB',
|
|
||||||
'json': 'CLOB',
|
|
||||||
'password': 'VARCHAR2(%(length)s)',
|
|
||||||
'blob': 'CLOB',
|
|
||||||
'upload': 'VARCHAR2(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'NUMBER',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'BINARY_DOUBLE',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'CHAR(8)',
|
|
||||||
'datetime': 'DATE',
|
|
||||||
'id': 'NUMBER PRIMARY KEY',
|
|
||||||
'reference': 'NUMBER, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'CLOB',
|
|
||||||
'list:string': 'CLOB',
|
|
||||||
'list:reference': 'CLOB',
|
|
||||||
'big-id': 'NUMBER PRIMARY KEY',
|
|
||||||
'big-reference': 'NUMBER, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def trigger_name(self,tablename):
|
|
||||||
return '%s_trigger' % tablename
|
|
||||||
|
|
||||||
def LEFT_JOIN(self):
|
|
||||||
return 'LEFT OUTER JOIN'
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'dbms_random.value'
|
|
||||||
|
|
||||||
def NOT_NULL(self,default,field_type):
|
|
||||||
return 'DEFAULT %s NOT NULL' % self.represent(default,field_type)
|
|
||||||
|
|
||||||
def REGEXP(self, first, second):
|
|
||||||
return 'REGEXP_LIKE(%s, %s)' % (self.expand(first),
|
|
||||||
self.expand(second, 'string'))
|
|
||||||
|
|
||||||
def _drop(self,table,mode):
|
|
||||||
sequence_name = table._sequence_name
|
|
||||||
return ['DROP TABLE %s %s;' % (table.sqlsafe, mode), 'DROP SEQUENCE %s;' % sequence_name]
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
if len(sql_w) > 1:
|
|
||||||
sql_w_row = sql_w + ' AND w_row > %i' % lmin
|
|
||||||
else:
|
|
||||||
sql_w_row = 'WHERE w_row > %i' % lmin
|
|
||||||
return 'SELECT %s %s FROM (SELECT w_tmp.*, ROWNUM w_row FROM (SELECT %s FROM %s%s%s) w_tmp WHERE ROWNUM<=%i) %s %s %s;' % (sql_s, sql_f, sql_f, sql_t, sql_w, sql_o, lmax, sql_t, sql_w_row, sql_o)
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def constraint_name(self, tablename, fieldname):
|
|
||||||
constraint_name = BaseAdapter.constraint_name(self, tablename, fieldname)
|
|
||||||
if len(constraint_name)>30:
|
|
||||||
constraint_name = '%s_%s__constraint' % (tablename[:10], fieldname[:7])
|
|
||||||
return constraint_name
|
|
||||||
|
|
||||||
def represent_exceptions(self, obj, fieldtype):
|
|
||||||
if fieldtype == 'blob':
|
|
||||||
obj = base64.b64encode(str(obj))
|
|
||||||
return ":CLOB('%s')" % obj
|
|
||||||
elif fieldtype == 'date':
|
|
||||||
if isinstance(obj, (datetime.date, datetime.datetime)):
|
|
||||||
obj = obj.isoformat()[:10]
|
|
||||||
else:
|
|
||||||
obj = str(obj)
|
|
||||||
return "to_date('%s','yyyy-mm-dd')" % obj
|
|
||||||
elif fieldtype == 'datetime':
|
|
||||||
if isinstance(obj, datetime.datetime):
|
|
||||||
obj = obj.isoformat()[:19].replace('T',' ')
|
|
||||||
elif isinstance(obj, datetime.date):
|
|
||||||
obj = obj.isoformat()[:10]+' 00:00:00'
|
|
||||||
else:
|
|
||||||
obj = str(obj)
|
|
||||||
return "to_date('%s','yyyy-mm-dd hh24:mi:ss')" % obj
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "oracle"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
if not 'threaded' in driver_args:
|
|
||||||
driver_args['threaded']=True
|
|
||||||
def connector(uri=ruri,driver_args=driver_args):
|
|
||||||
return self.driver.connect(uri,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';")
|
|
||||||
self.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS';")
|
|
||||||
|
|
||||||
oracle_fix = re.compile("[^']*('[^']*'[^']*)*\:(?P<clob>CLOB\('([^']+|'')*'\))")
|
|
||||||
|
|
||||||
def execute(self, command, args=None):
|
|
||||||
args = args or []
|
|
||||||
i = 1
|
|
||||||
while True:
|
|
||||||
m = self.oracle_fix.match(command)
|
|
||||||
if not m:
|
|
||||||
break
|
|
||||||
command = command[:m.start('clob')] + str(i) + command[m.end('clob'):]
|
|
||||||
args.append(m.group('clob')[6:-2].replace("''", "'"))
|
|
||||||
i += 1
|
|
||||||
if command[-1:]==';':
|
|
||||||
command = command[:-1]
|
|
||||||
return self.log_execute(command, args)
|
|
||||||
|
|
||||||
def create_sequence_and_triggers(self, query, table, **args):
|
|
||||||
tablename = table._rname or table._tablename
|
|
||||||
id_name = table._id.name
|
|
||||||
sequence_name = table._sequence_name
|
|
||||||
trigger_name = table._trigger_name
|
|
||||||
self.execute(query)
|
|
||||||
self.execute('CREATE SEQUENCE %s START WITH 1 INCREMENT BY 1 NOMAXVALUE MINVALUE -1;' % sequence_name)
|
|
||||||
self.execute("""
|
|
||||||
CREATE OR REPLACE TRIGGER %(trigger_name)s BEFORE INSERT ON %(tablename)s FOR EACH ROW
|
|
||||||
DECLARE
|
|
||||||
curr_val NUMBER;
|
|
||||||
diff_val NUMBER;
|
|
||||||
PRAGMA autonomous_transaction;
|
|
||||||
BEGIN
|
|
||||||
IF :NEW.%(id)s IS NOT NULL THEN
|
|
||||||
EXECUTE IMMEDIATE 'SELECT %(sequence_name)s.nextval FROM dual' INTO curr_val;
|
|
||||||
diff_val := :NEW.%(id)s - curr_val - 1;
|
|
||||||
IF diff_val != 0 THEN
|
|
||||||
EXECUTE IMMEDIATE 'alter sequence %(sequence_name)s increment by '|| diff_val;
|
|
||||||
EXECUTE IMMEDIATE 'SELECT %(sequence_name)s.nextval FROM dual' INTO curr_val;
|
|
||||||
EXECUTE IMMEDIATE 'alter sequence %(sequence_name)s increment by 1';
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
SELECT %(sequence_name)s.nextval INTO :NEW.%(id)s FROM DUAL;
|
|
||||||
END;
|
|
||||||
""" % dict(trigger_name=trigger_name, tablename=tablename,
|
|
||||||
sequence_name=sequence_name,id=id_name))
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
sequence_name = table._sequence_name
|
|
||||||
self.execute('SELECT %s.currval FROM dual;' % sequence_name)
|
|
||||||
return long(self.cursor.fetchone()[0])
|
|
||||||
|
|
||||||
#def parse_value(self, value, field_type, blob_decode=True):
|
|
||||||
# if blob_decode and isinstance(value, cx_Oracle.LOB):
|
|
||||||
# try:
|
|
||||||
# value = value.read()
|
|
||||||
# except self.driver.ProgrammingError:
|
|
||||||
# # After a subsequent fetch the LOB value is not valid anymore
|
|
||||||
# pass
|
|
||||||
# return BaseAdapter.parse_value(self, value, field_type, blob_decode)
|
|
||||||
|
|
||||||
def _fetchall(self):
|
|
||||||
if any(x[1]==cx_Oracle.LOB for x in self.cursor.description):
|
|
||||||
return [tuple([(c.read() if type(c) == cx_Oracle.LOB else c) \
|
|
||||||
for c in r]) for r in self.cursor]
|
|
||||||
else:
|
|
||||||
return self.cursor.fetchall()
|
|
||||||
|
|
||||||
def sqlsafe_table(self, tablename, ot=None):
|
|
||||||
if ot is not None:
|
|
||||||
return (self.QUOTE_TEMPLATE + ' ' \
|
|
||||||
+ self.QUOTE_TEMPLATE) % (ot, tablename)
|
|
||||||
return self.QUOTE_TEMPLATE % tablename
|
|
||||||
@@ -1,420 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._load import psycopg2_adapt
|
|
||||||
from .._globals import IDENTITY, LOGGER
|
|
||||||
from ..helpers.methods import varquote_aux
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class PostgreSQLAdapter(BaseAdapter):
|
|
||||||
drivers = ('psycopg2','pg8000')
|
|
||||||
|
|
||||||
QUOTE_TEMPLATE = '"%s"'
|
|
||||||
|
|
||||||
support_distributed_transaction = True
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'TEXT',
|
|
||||||
'json': 'TEXT',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BYTEA',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INTEGER',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT8',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'TIMESTAMP',
|
|
||||||
'id': 'SERIAL PRIMARY KEY',
|
|
||||||
'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'TEXT',
|
|
||||||
'list:string': 'TEXT',
|
|
||||||
'list:reference': 'TEXT',
|
|
||||||
'geometry': 'GEOMETRY',
|
|
||||||
'geography': 'GEOGRAPHY',
|
|
||||||
'big-id': 'BIGSERIAL PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference FK': ', CONSTRAINT "FK_%(constraint_name)s" FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'reference TFK': ' CONSTRAINT "FK_%(foreign_table)s_PK" FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def varquote(self,name):
|
|
||||||
return varquote_aux(name,'"%s"')
|
|
||||||
|
|
||||||
def adapt(self,obj):
|
|
||||||
if self.driver_name == 'psycopg2':
|
|
||||||
return psycopg2_adapt(obj).getquoted()
|
|
||||||
elif self.driver_name == 'pg8000':
|
|
||||||
return "'%s'" % str(obj).replace("%","%%").replace("'","''")
|
|
||||||
else:
|
|
||||||
return "'%s'" % str(obj).replace("'","''")
|
|
||||||
|
|
||||||
def sequence_name(self,table):
|
|
||||||
return self.QUOTE_TEMPLATE % (table + '_id_seq')
|
|
||||||
|
|
||||||
def RANDOM(self):
|
|
||||||
return 'RANDOM()'
|
|
||||||
|
|
||||||
def ADD(self, first, second):
|
|
||||||
t = first.type
|
|
||||||
if t in ('text','string','password', 'json', 'upload','blob'):
|
|
||||||
return '(%s || %s)' % (self.expand(first), self.expand(second, t))
|
|
||||||
else:
|
|
||||||
return '(%s + %s)' % (self.expand(first), self.expand(second, t))
|
|
||||||
|
|
||||||
def distributed_transaction_begin(self,key):
|
|
||||||
return
|
|
||||||
|
|
||||||
def prepare(self,key):
|
|
||||||
self.execute("PREPARE TRANSACTION '%s';" % key)
|
|
||||||
|
|
||||||
def commit_prepared(self,key):
|
|
||||||
self.execute("COMMIT PREPARED '%s';" % key)
|
|
||||||
|
|
||||||
def rollback_prepared(self,key):
|
|
||||||
self.execute("ROLLBACK PREPARED '%s';" % key)
|
|
||||||
|
|
||||||
def create_sequence_and_triggers(self, query, table, **args):
|
|
||||||
# following lines should only be executed if table._sequence_name does not exist
|
|
||||||
# self.execute('CREATE SEQUENCE %s;' % table._sequence_name)
|
|
||||||
# self.execute("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" \
|
|
||||||
# % (table._tablename, table._fieldname, table._sequence_name))
|
|
||||||
self.execute(query)
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:@]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?sslmode=(?P<sslmode>.+))?$')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, srid=4326,
|
|
||||||
after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "postgres"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.srid = srid
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
self._last_insert = None # for INSERT ... RETURNING ID
|
|
||||||
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError("Invalid URI string in DAL")
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
port = m.group('port') or '5432'
|
|
||||||
sslmode = m.group('sslmode')
|
|
||||||
if sslmode:
|
|
||||||
msg = ("dbname='%s' user='%s' host='%s' "
|
|
||||||
"port=%s password='%s' sslmode='%s'") \
|
|
||||||
% (db, user, host, port, password, sslmode)
|
|
||||||
else:
|
|
||||||
msg = ("dbname='%s' user='%s' host='%s' "
|
|
||||||
"port=%s password='%s'") \
|
|
||||||
% (db, user, host, port, password)
|
|
||||||
# choose diver according uri
|
|
||||||
if self.driver:
|
|
||||||
self.__version__ = "%s %s" % (self.driver.__name__,
|
|
||||||
self.driver.__version__)
|
|
||||||
else:
|
|
||||||
self.__version__ = None
|
|
||||||
def connector(msg=msg,driver_args=driver_args):
|
|
||||||
return self.driver.connect(msg,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.connection.set_client_encoding('UTF8')
|
|
||||||
self.execute("SET standard_conforming_strings=on;")
|
|
||||||
self.try_json()
|
|
||||||
|
|
||||||
def _insert(self, table, fields):
|
|
||||||
table_rname = table.sqlsafe
|
|
||||||
if fields:
|
|
||||||
keys = ','.join(f.sqlsafe_name for f, v in fields)
|
|
||||||
values = ','.join(self.expand(v, f.type) for f, v in fields)
|
|
||||||
if table._id:
|
|
||||||
self._last_insert = (table._id, 1)
|
|
||||||
return 'INSERT INTO %s(%s) VALUES (%s) RETURNING %s;' % (
|
|
||||||
table_rname, keys, values, table._id.name)
|
|
||||||
else:
|
|
||||||
self._last_insert = None
|
|
||||||
return 'INSERT INTO %s(%s) VALUES (%s);' % (table_rname, keys, values)
|
|
||||||
else:
|
|
||||||
self._last_insert
|
|
||||||
return self._insert_empty(table)
|
|
||||||
|
|
||||||
def lastrowid(self, table=None):
|
|
||||||
if self._last_insert:
|
|
||||||
return int(self.cursor.fetchone()[0])
|
|
||||||
else:
|
|
||||||
self.execute("select lastval()")
|
|
||||||
return int(self.cursor.fetchone()[0])
|
|
||||||
|
|
||||||
def try_json(self):
|
|
||||||
# check JSON data type support
|
|
||||||
# (to be added to after_connection)
|
|
||||||
|
|
||||||
# until pg8000 supports json, leave this commented
|
|
||||||
#if self.driver_name == "pg8000":
|
|
||||||
# supports_json = self.connection.server_version >= "9.2.0"
|
|
||||||
|
|
||||||
if (self.driver_name == "psycopg2" and
|
|
||||||
self.driver.__version__ >= "2.0.12"):
|
|
||||||
supports_json = self.connection.server_version >= 90200
|
|
||||||
elif self.driver_name == "zxJDBC":
|
|
||||||
supports_json = self.connection.dbversion >= "9.2.0"
|
|
||||||
else:
|
|
||||||
supports_json = None
|
|
||||||
if supports_json:
|
|
||||||
self.types["json"] = "JSON"
|
|
||||||
if (self.driver_name == "psycopg2" and
|
|
||||||
self.driver.__version__ >= '2.5.0'):
|
|
||||||
self.driver_auto_json = ['loads']
|
|
||||||
else:
|
|
||||||
LOGGER.debug("Your database version does not support the JSON"
|
|
||||||
" data type (using TEXT instead)")
|
|
||||||
|
|
||||||
def LIKE(self,first,second):
|
|
||||||
args = (self.expand(first), self.expand(second,'string'))
|
|
||||||
if not first.type in ('string', 'text', 'json'):
|
|
||||||
return '(%s LIKE %s)' % (
|
|
||||||
self.CAST(args[0], 'CHAR(%s)' % first.length), args[1])
|
|
||||||
else:
|
|
||||||
return '(%s LIKE %s)' % args
|
|
||||||
|
|
||||||
def ILIKE(self,first,second):
|
|
||||||
args = (self.expand(first), self.expand(second,'string'))
|
|
||||||
if not first.type in ('string', 'text', 'json'):
|
|
||||||
return '(%s LIKE %s)' % (
|
|
||||||
self.CAST(args[0], 'CHAR(%s)' % first.length), args[1])
|
|
||||||
else:
|
|
||||||
return '(%s ILIKE %s)' % args
|
|
||||||
|
|
||||||
def REGEXP(self,first,second):
|
|
||||||
return '(%s ~ %s)' % (self.expand(first),
|
|
||||||
self.expand(second,'string'))
|
|
||||||
|
|
||||||
# GIS functions
|
|
||||||
|
|
||||||
def ST_ASGEOJSON(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_AsGeoJSON.html
|
|
||||||
"""
|
|
||||||
return 'ST_AsGeoJSON(%s,%s,%s,%s)' %(second['version'],
|
|
||||||
self.expand(first), second['precision'], second['options'])
|
|
||||||
|
|
||||||
def ST_ASTEXT(self, first):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_AsText.html
|
|
||||||
"""
|
|
||||||
return 'ST_AsText(%s)' %(self.expand(first))
|
|
||||||
|
|
||||||
def ST_X(self, first):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_X.html
|
|
||||||
"""
|
|
||||||
return 'ST_X(%s)' %(self.expand(first))
|
|
||||||
|
|
||||||
def ST_Y(self, first):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Y.html
|
|
||||||
"""
|
|
||||||
return 'ST_Y(%s)' %(self.expand(first))
|
|
||||||
|
|
||||||
def ST_CONTAINS(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Contains.html
|
|
||||||
"""
|
|
||||||
return 'ST_Contains(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_DISTANCE(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Distance.html
|
|
||||||
"""
|
|
||||||
return 'ST_Distance(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_EQUALS(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Equals.html
|
|
||||||
"""
|
|
||||||
return 'ST_Equals(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_INTERSECTS(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Intersects.html
|
|
||||||
"""
|
|
||||||
return 'ST_Intersects(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_OVERLAPS(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Overlaps.html
|
|
||||||
"""
|
|
||||||
return 'ST_Overlaps(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_SIMPLIFY(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Simplify.html
|
|
||||||
"""
|
|
||||||
return 'ST_Simplify(%s,%s)' %(self.expand(first), self.expand(second, 'double'))
|
|
||||||
|
|
||||||
def ST_TOUCHES(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Touches.html
|
|
||||||
"""
|
|
||||||
return 'ST_Touches(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_WITHIN(self, first, second):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_Within.html
|
|
||||||
"""
|
|
||||||
return 'ST_Within(%s,%s)' %(self.expand(first), self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_DWITHIN(self, first, (second, third)):
|
|
||||||
"""
|
|
||||||
http://postgis.org/docs/ST_DWithin.html
|
|
||||||
"""
|
|
||||||
return 'ST_DWithin(%s,%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type),
|
|
||||||
self.expand(third, 'double'))
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
field_is_type = fieldtype.startswith
|
|
||||||
if field_is_type('geo'):
|
|
||||||
srid = 4326 # postGIS default srid for geometry
|
|
||||||
geotype, parms = fieldtype[:-1].split('(')
|
|
||||||
parms = parms.split(',')
|
|
||||||
if len(parms) >= 2:
|
|
||||||
schema, srid = parms[:2]
|
|
||||||
if field_is_type('geometry'):
|
|
||||||
value = "ST_GeomFromText('%s',%s)" %(obj, srid)
|
|
||||||
elif field_is_type('geography'):
|
|
||||||
value = "ST_GeogFromText('SRID=%s;%s')" %(srid, obj)
|
|
||||||
# else:
|
|
||||||
# raise SyntaxError('Invalid field type %s' %fieldtype)
|
|
||||||
return value
|
|
||||||
return BaseAdapter.represent(self, obj, fieldtype)
|
|
||||||
|
|
||||||
def _drop(self, table, mode='restrict'):
|
|
||||||
if mode not in ['restrict', 'cascade', '']:
|
|
||||||
raise ValueError('Invalid mode: %s' % mode)
|
|
||||||
return ['DROP TABLE ' + table.sqlsafe + ' ' + str(mode) + ';']
|
|
||||||
|
|
||||||
class NewPostgreSQLAdapter(PostgreSQLAdapter):
|
|
||||||
drivers = ('psycopg2','pg8000')
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'TEXT',
|
|
||||||
'json': 'TEXT',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BYTEA',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INTEGER',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'FLOAT8',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'TIMESTAMP',
|
|
||||||
'id': 'SERIAL PRIMARY KEY',
|
|
||||||
'reference': 'INTEGER REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'BIGINT[]',
|
|
||||||
'list:string': 'TEXT[]',
|
|
||||||
'list:reference': 'BIGINT[]',
|
|
||||||
'geometry': 'GEOMETRY',
|
|
||||||
'geography': 'GEOGRAPHY',
|
|
||||||
'big-id': 'BIGSERIAL PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse_list_integers(self, value, field_type):
|
|
||||||
return value
|
|
||||||
|
|
||||||
def parse_list_references(self, value, field_type):
|
|
||||||
return [self.parse_reference(r, field_type[5:]) for r in value]
|
|
||||||
|
|
||||||
def parse_list_strings(self, value, field_type):
|
|
||||||
return value
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
field_is_type = fieldtype.startswith
|
|
||||||
if field_is_type('list:'):
|
|
||||||
if not obj:
|
|
||||||
obj = []
|
|
||||||
elif not isinstance(obj, (list, tuple)):
|
|
||||||
obj = [obj]
|
|
||||||
if field_is_type('list:string'):
|
|
||||||
obj = map(str,obj)
|
|
||||||
else:
|
|
||||||
obj = map(int,obj)
|
|
||||||
return 'ARRAY[%s]' % ','.join(repr(item) for item in obj)
|
|
||||||
return BaseAdapter.represent(self, obj, fieldtype)
|
|
||||||
|
|
||||||
|
|
||||||
class JDBCPostgreSQLAdapter(PostgreSQLAdapter):
|
|
||||||
drivers = ('zxJDBC',)
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>.+)$')
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None ):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "postgres"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError("Invalid URI string in DAL")
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
port = m.group('port') or '5432'
|
|
||||||
msg = ('jdbc:postgresql://%s:%s/%s' % (host, port, db), user, password)
|
|
||||||
def connector(msg=msg,driver_args=driver_args):
|
|
||||||
return self.driver.connect(*msg,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.connection.set_client_encoding('UTF8')
|
|
||||||
self.execute('BEGIN;')
|
|
||||||
self.execute("SET CLIENT_ENCODING TO 'UNICODE';")
|
|
||||||
self.try_json()
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class SAPDBAdapter(BaseAdapter):
|
|
||||||
drivers = ('sapdb',)
|
|
||||||
|
|
||||||
support_distributed_transaction = False
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'LONG',
|
|
||||||
'json': 'LONG',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'LONG',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'FLOAT',
|
|
||||||
'double': 'DOUBLE PRECISION',
|
|
||||||
'decimal': 'FIXED(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'TIMESTAMP',
|
|
||||||
'id': 'INT PRIMARY KEY',
|
|
||||||
'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'LONG',
|
|
||||||
'list:string': 'LONG',
|
|
||||||
'list:reference': 'LONG',
|
|
||||||
'big-id': 'BIGINT PRIMARY KEY',
|
|
||||||
'big-reference': 'BIGINT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
|
||||||
}
|
|
||||||
|
|
||||||
def sequence_name(self,table):
|
|
||||||
return (self.QUOTE_TEMPLATE + '_id_Seq') % table
|
|
||||||
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
if len(sql_w) > 1:
|
|
||||||
sql_w_row = sql_w + ' AND w_row > %i' % lmin
|
|
||||||
else:
|
|
||||||
sql_w_row = 'WHERE w_row > %i' % lmin
|
|
||||||
return '%s %s FROM (SELECT w_tmp.*, ROWNO w_row FROM (SELECT %s FROM %s%s%s) w_tmp WHERE ROWNO=%i) %s %s %s;' % (sql_s, sql_f, sql_f, sql_t, sql_w, sql_o, lmax, sql_t, sql_w_row, sql_o)
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def create_sequence_and_triggers(self, query, table, **args):
|
|
||||||
# following lines should only be executed if table._sequence_name does not exist
|
|
||||||
self.execute('CREATE SEQUENCE %s;' % table._sequence_name)
|
|
||||||
self.execute("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" \
|
|
||||||
% (table._tablename, table._id.name, table._sequence_name))
|
|
||||||
self.execute(query)
|
|
||||||
|
|
||||||
REGEX_URI = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:@]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?sslmode=(?P<sslmode>.+))?$')
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "sapdb"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://',1)[1]
|
|
||||||
m = self.REGEX_URI.match(ruri)
|
|
||||||
if not m:
|
|
||||||
raise SyntaxError("Invalid URI string in DAL")
|
|
||||||
user = credential_decoder(m.group('user'))
|
|
||||||
if not user:
|
|
||||||
raise SyntaxError('User required')
|
|
||||||
password = credential_decoder(m.group('password'))
|
|
||||||
if not password:
|
|
||||||
password = ''
|
|
||||||
host = m.group('host')
|
|
||||||
if not host:
|
|
||||||
raise SyntaxError('Host name required')
|
|
||||||
db = m.group('db')
|
|
||||||
if not db:
|
|
||||||
raise SyntaxError('Database name required')
|
|
||||||
def connector(user=user, password=password, database=db,
|
|
||||||
host=host, driver_args=driver_args):
|
|
||||||
return self.driver.Connection(user, password, database,
|
|
||||||
host, **driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def lastrowid(self,table):
|
|
||||||
self.execute("select %s.NEXTVAL from dual" % table._sequence_name)
|
|
||||||
return long(self.cursor.fetchone()[0])
|
|
||||||
@@ -1,280 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import copy
|
|
||||||
import datetime
|
|
||||||
import locale
|
|
||||||
import platform
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
from .._compat import PY2, pjoin
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class SQLiteAdapter(BaseAdapter):
|
|
||||||
drivers = ('sqlite2','sqlite3')
|
|
||||||
|
|
||||||
can_select_for_update = None # support ourselves with BEGIN TRANSACTION
|
|
||||||
|
|
||||||
def EXTRACT(self,field,what):
|
|
||||||
return "web2py_extract('%s',%s)" % (what, self.expand(field))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def web2py_extract(lookup, s):
|
|
||||||
table = {
|
|
||||||
'year': (0, 4),
|
|
||||||
'month': (5, 7),
|
|
||||||
'day': (8, 10),
|
|
||||||
'hour': (11, 13),
|
|
||||||
'minute': (14, 16),
|
|
||||||
'second': (17, 19),
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
if lookup != 'epoch':
|
|
||||||
(i, j) = table[lookup]
|
|
||||||
return int(s[i:j])
|
|
||||||
else:
|
|
||||||
return time.mktime(datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S').timetuple())
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def web2py_regexp(expression, item):
|
|
||||||
return re.compile(expression).search(item) is not None
|
|
||||||
|
|
||||||
def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "sqlite"
|
|
||||||
self.uri = uri
|
|
||||||
self.adapter_args = adapter_args
|
|
||||||
if do_connect: self.find_driver(adapter_args)
|
|
||||||
self.pool_size = 0
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
path_encoding = sys.getfilesystemencoding() \
|
|
||||||
or locale.getdefaultlocale()[1] or 'utf8'
|
|
||||||
if uri.startswith('sqlite:memory'):
|
|
||||||
self.dbpath = ':memory:'
|
|
||||||
else:
|
|
||||||
self.dbpath = uri.split('://',1)[1]
|
|
||||||
if self.dbpath[0] != '/':
|
|
||||||
if PY2:
|
|
||||||
self.dbpath = pjoin(
|
|
||||||
self.folder.decode(path_encoding).encode('utf8'), self.dbpath)
|
|
||||||
else:
|
|
||||||
self.dbpath = pjoin(self.folder, self.dbpath)
|
|
||||||
if not 'check_same_thread' in driver_args:
|
|
||||||
driver_args['check_same_thread'] = False
|
|
||||||
if not 'detect_types' in driver_args and do_connect:
|
|
||||||
driver_args['detect_types'] = self.driver.PARSE_DECLTYPES
|
|
||||||
def connector(dbpath=self.dbpath, driver_args=driver_args):
|
|
||||||
return self.driver.Connection(dbpath, **driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.connection.create_function('web2py_extract', 2,
|
|
||||||
SQLiteAdapter.web2py_extract)
|
|
||||||
self.connection.create_function("REGEXP", 2,
|
|
||||||
SQLiteAdapter.web2py_regexp)
|
|
||||||
|
|
||||||
if self.adapter_args.get('foreign_keys',True):
|
|
||||||
self.execute('PRAGMA foreign_keys=ON;')
|
|
||||||
|
|
||||||
def _truncate(self, table, mode=''):
|
|
||||||
tablename = table._tablename
|
|
||||||
return ['DELETE FROM %s;' % tablename,
|
|
||||||
"DELETE FROM sqlite_sequence WHERE name='%s';" % tablename]
|
|
||||||
|
|
||||||
def lastrowid(self, table):
|
|
||||||
return self.cursor.lastrowid
|
|
||||||
|
|
||||||
def REGEXP(self,first,second):
|
|
||||||
return '(%s REGEXP %s)' % (self.expand(first),
|
|
||||||
self.expand(second,'string'))
|
|
||||||
|
|
||||||
def delete(self, tablename, query):
|
|
||||||
# SQLite requires its own delete to handle CASCADE
|
|
||||||
db = self.db
|
|
||||||
table = db[tablename]
|
|
||||||
deleted = [x[table._id.name] for x in db(query).select(table._id)]
|
|
||||||
|
|
||||||
counter = super(SQLiteAdapter, self).delete(tablename, query)
|
|
||||||
|
|
||||||
if counter:
|
|
||||||
for field in table._referenced_by:
|
|
||||||
if field.type == 'reference '+ tablename \
|
|
||||||
and field.ondelete == 'CASCADE':
|
|
||||||
db(field.belongs(deleted)).delete()
|
|
||||||
|
|
||||||
return counter
|
|
||||||
|
|
||||||
def select(self, query, fields, attributes):
|
|
||||||
"""
|
|
||||||
Simulate `SELECT ... FOR UPDATE` with `BEGIN IMMEDIATE TRANSACTION`.
|
|
||||||
Note that the entire database, rather than one record, is locked
|
|
||||||
(it will be locked eventually anyway by the following UPDATE).
|
|
||||||
"""
|
|
||||||
if attributes.get('for_update', False) and not 'cache' in attributes:
|
|
||||||
self.execute('BEGIN IMMEDIATE TRANSACTION;')
|
|
||||||
return super(SQLiteAdapter, self).select(query, fields, attributes)
|
|
||||||
|
|
||||||
|
|
||||||
SPATIALLIBS = {
|
|
||||||
'Windows':'libspatialite',
|
|
||||||
'Linux':'libspatialite.so',
|
|
||||||
'Darwin':'libspatialite.dylib'
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpatiaLiteAdapter(SQLiteAdapter):
|
|
||||||
drivers = ('sqlite3','sqlite2')
|
|
||||||
|
|
||||||
types = copy.copy(BaseAdapter.types)
|
|
||||||
types.update(geometry='GEOMETRY')
|
|
||||||
|
|
||||||
def __init__(self, db, uri, pool_size=0, folder=None, db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, srid=4326, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "spatialite"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args)
|
|
||||||
self.pool_size = 0
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
self.srid = srid
|
|
||||||
path_encoding = sys.getfilesystemencoding() \
|
|
||||||
or locale.getdefaultlocale()[1] or 'utf8'
|
|
||||||
if uri.startswith('spatialite:memory'):
|
|
||||||
self.dbpath = ':memory:'
|
|
||||||
else:
|
|
||||||
self.dbpath = uri.split('://',1)[1]
|
|
||||||
if self.dbpath[0] != '/':
|
|
||||||
self.dbpath = pjoin(
|
|
||||||
self.folder.decode(path_encoding).encode('utf8'), self.dbpath)
|
|
||||||
if not 'check_same_thread' in driver_args:
|
|
||||||
driver_args['check_same_thread'] = False
|
|
||||||
if not 'detect_types' in driver_args and do_connect:
|
|
||||||
driver_args['detect_types'] = self.driver.PARSE_DECLTYPES
|
|
||||||
def connector(dbpath=self.dbpath, driver_args=driver_args):
|
|
||||||
return self.driver.Connection(dbpath, **driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
self.connection.enable_load_extension(True)
|
|
||||||
# for Windows, rename libspatialite-2.dll to libspatialite.dll
|
|
||||||
# Linux uses libspatialite.so
|
|
||||||
# Mac OS X uses libspatialite.dylib
|
|
||||||
libspatialite = SPATIALLIBS[platform.system()]
|
|
||||||
self.execute(r'SELECT load_extension("%s");' % libspatialite)
|
|
||||||
|
|
||||||
self.connection.create_function('web2py_extract', 2,
|
|
||||||
SQLiteAdapter.web2py_extract)
|
|
||||||
self.connection.create_function("REGEXP", 2,
|
|
||||||
SQLiteAdapter.web2py_regexp)
|
|
||||||
|
|
||||||
# GIS functions
|
|
||||||
|
|
||||||
def ST_ASGEOJSON(self, first, second):
|
|
||||||
return 'AsGeoJSON(%s,%s,%s)' %(self.expand(first),
|
|
||||||
second['precision'], second['options'])
|
|
||||||
|
|
||||||
def ST_ASTEXT(self, first):
|
|
||||||
return 'AsText(%s)' %(self.expand(first))
|
|
||||||
|
|
||||||
def ST_CONTAINS(self, first, second):
|
|
||||||
return 'Contains(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_DISTANCE(self, first, second):
|
|
||||||
return 'Distance(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_EQUALS(self, first, second):
|
|
||||||
return 'Equals(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_INTERSECTS(self, first, second):
|
|
||||||
return 'Intersects(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_OVERLAPS(self, first, second):
|
|
||||||
return 'Overlaps(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_SIMPLIFY(self, first, second):
|
|
||||||
return 'Simplify(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, 'double'))
|
|
||||||
|
|
||||||
def ST_TOUCHES(self, first, second):
|
|
||||||
return 'Touches(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def ST_WITHIN(self, first, second):
|
|
||||||
return 'Within(%s,%s)' %(self.expand(first),
|
|
||||||
self.expand(second, first.type))
|
|
||||||
|
|
||||||
def represent(self, obj, fieldtype):
|
|
||||||
field_is_type = fieldtype.startswith
|
|
||||||
if field_is_type('geo'):
|
|
||||||
srid = 4326 # Spatialite default srid for geometry
|
|
||||||
geotype, parms = fieldtype[:-1].split('(')
|
|
||||||
parms = parms.split(',')
|
|
||||||
if len(parms) >= 2:
|
|
||||||
schema, srid = parms[:2]
|
|
||||||
# if field_is_type('geometry'):
|
|
||||||
value = "ST_GeomFromText('%s',%s)" %(obj, srid)
|
|
||||||
# elif field_is_type('geography'):
|
|
||||||
# value = "ST_GeogFromText('SRID=%s;%s')" %(srid, obj)
|
|
||||||
# else:
|
|
||||||
# raise SyntaxError, 'Invalid field type %s' %fieldtype
|
|
||||||
return value
|
|
||||||
return BaseAdapter.represent(self, obj, fieldtype)
|
|
||||||
|
|
||||||
|
|
||||||
class JDBCSQLiteAdapter(SQLiteAdapter):
|
|
||||||
drivers = ('zxJDBC_sqlite',)
|
|
||||||
|
|
||||||
def __init__(self, db, uri, pool_size=0, folder=None, db_codec='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "sqlite"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
path_encoding = sys.getfilesystemencoding() \
|
|
||||||
or locale.getdefaultlocale()[1] or 'utf8'
|
|
||||||
if uri.startswith('sqlite:memory'):
|
|
||||||
self.dbpath = ':memory:'
|
|
||||||
else:
|
|
||||||
self.dbpath = uri.split('://',1)[1]
|
|
||||||
if self.dbpath[0] != '/':
|
|
||||||
self.dbpath = pjoin(
|
|
||||||
self.folder.decode(path_encoding).encode('utf8'), self.dbpath)
|
|
||||||
def connector(dbpath=self.dbpath,driver_args=driver_args):
|
|
||||||
return self.driver.connect(
|
|
||||||
self.driver.getConnection('jdbc:sqlite:'+dbpath),
|
|
||||||
**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
# FIXME http://www.zentus.com/sqlitejdbc/custom_functions.html for UDFs
|
|
||||||
self.connection.create_function('web2py_extract', 2,
|
|
||||||
SQLiteAdapter.web2py_extract)
|
|
||||||
|
|
||||||
def execute(self, a):
|
|
||||||
return self.log_execute(a)
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from .._globals import IDENTITY
|
|
||||||
from ..connection import ConnectionPool
|
|
||||||
from .base import BaseAdapter
|
|
||||||
|
|
||||||
|
|
||||||
class TeradataAdapter(BaseAdapter):
|
|
||||||
drivers = ('pyodbc',)
|
|
||||||
|
|
||||||
types = {
|
|
||||||
'boolean': 'CHAR(1)',
|
|
||||||
'string': 'VARCHAR(%(length)s)',
|
|
||||||
'text': 'VARCHAR(2000)',
|
|
||||||
'json': 'VARCHAR(4000)',
|
|
||||||
'password': 'VARCHAR(%(length)s)',
|
|
||||||
'blob': 'BLOB',
|
|
||||||
'upload': 'VARCHAR(%(length)s)',
|
|
||||||
'integer': 'INT',
|
|
||||||
'bigint': 'BIGINT',
|
|
||||||
'float': 'REAL',
|
|
||||||
'double': 'DOUBLE',
|
|
||||||
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
|
|
||||||
'date': 'DATE',
|
|
||||||
'time': 'TIME',
|
|
||||||
'datetime': 'TIMESTAMP',
|
|
||||||
# Modified Constraint syntax for Teradata.
|
|
||||||
# Teradata does not support ON DELETE.
|
|
||||||
'id': 'INT GENERATED ALWAYS AS IDENTITY', # Teradata Specific
|
|
||||||
'reference': 'INT',
|
|
||||||
'list:integer': 'VARCHAR(4000)',
|
|
||||||
'list:string': 'VARCHAR(4000)',
|
|
||||||
'list:reference': 'VARCHAR(4000)',
|
|
||||||
'big-id': 'BIGINT GENERATED ALWAYS AS IDENTITY', # Teradata Specific
|
|
||||||
'big-reference': 'BIGINT',
|
|
||||||
'reference FK': ' REFERENCES %(foreign_key)s',
|
|
||||||
'reference TFK': ' FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s)',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
|
||||||
credential_decoder=IDENTITY, driver_args={},
|
|
||||||
adapter_args={}, do_connect=True, after_connection=None):
|
|
||||||
self.db = db
|
|
||||||
self.dbengine = "teradata"
|
|
||||||
self.uri = uri
|
|
||||||
if do_connect: self.find_driver(adapter_args,uri)
|
|
||||||
self.pool_size = pool_size
|
|
||||||
self.folder = folder
|
|
||||||
self.db_codec = db_codec
|
|
||||||
self._after_connection = after_connection
|
|
||||||
self.find_or_make_work_folder()
|
|
||||||
ruri = uri.split('://', 1)[1]
|
|
||||||
def connector(cnxn=ruri,driver_args=driver_args):
|
|
||||||
return self.driver.connect(cnxn,**driver_args)
|
|
||||||
self.connector = connector
|
|
||||||
if do_connect: self.reconnect()
|
|
||||||
|
|
||||||
def close(self,action='commit',really=True):
|
|
||||||
# Teradata does not implicitly close off the cursor
|
|
||||||
# leading to SQL_ACTIVE_STATEMENTS limit errors
|
|
||||||
self.cursor.close()
|
|
||||||
ConnectionPool.close(self, action, really)
|
|
||||||
|
|
||||||
def LEFT_JOIN(self):
|
|
||||||
return 'LEFT OUTER JOIN'
|
|
||||||
|
|
||||||
# Similar to MSSQL, Teradata can't specify a range (for Pageby)
|
|
||||||
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
|
||||||
if limitby:
|
|
||||||
(lmin, lmax) = limitby
|
|
||||||
sql_s += ' TOP %i' % lmax
|
|
||||||
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
|
||||||
|
|
||||||
def _truncate(self, table, mode=''):
|
|
||||||
tablename = table._tablename
|
|
||||||
return ['DELETE FROM %s ALL;' % (tablename)]
|
|
||||||
-1095
File diff suppressed because it is too large
Load Diff
@@ -1,116 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ._compat import exists
|
|
||||||
from ._globals import GLOBAL_LOCKER, THREAD_LOCAL
|
|
||||||
from .helpers.classes import UseDatabaseStoredFile
|
|
||||||
|
|
||||||
class ConnectionPool(object):
|
|
||||||
|
|
||||||
POOLS = {}
|
|
||||||
check_active_connection = True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def set_folder(folder):
|
|
||||||
THREAD_LOCAL.folder = folder
|
|
||||||
|
|
||||||
# ## this allows gluon to commit/rollback all dbs in this thread
|
|
||||||
|
|
||||||
def close(self,action='commit',really=True):
|
|
||||||
if action:
|
|
||||||
if callable(action):
|
|
||||||
action(self)
|
|
||||||
else:
|
|
||||||
getattr(self, action)()
|
|
||||||
# ## if you want pools, recycle this connection
|
|
||||||
if self.pool_size:
|
|
||||||
GLOBAL_LOCKER.acquire()
|
|
||||||
pool = ConnectionPool.POOLS[self.uri]
|
|
||||||
if len(pool) < self.pool_size:
|
|
||||||
pool.append(self.connection)
|
|
||||||
really = False
|
|
||||||
GLOBAL_LOCKER.release()
|
|
||||||
if really:
|
|
||||||
self.close_connection()
|
|
||||||
self.connection = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def close_all_instances(action):
|
|
||||||
""" to close cleanly databases in a multithreaded environment """
|
|
||||||
dbs = getattr(THREAD_LOCAL,'db_instances',{}).items()
|
|
||||||
for db_uid, db_group in dbs:
|
|
||||||
for db in db_group:
|
|
||||||
if hasattr(db,'_adapter'):
|
|
||||||
db._adapter.close(action)
|
|
||||||
getattr(THREAD_LOCAL,'db_instances',{}).clear()
|
|
||||||
getattr(THREAD_LOCAL,'db_instances_zombie',{}).clear()
|
|
||||||
if callable(action):
|
|
||||||
action(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
def find_or_make_work_folder(self):
|
|
||||||
#this actually does not make the folder. it has to be there
|
|
||||||
self.folder = getattr(THREAD_LOCAL,'folder','')
|
|
||||||
|
|
||||||
if (os.path.isabs(self.folder) and
|
|
||||||
isinstance(self, UseDatabaseStoredFile) and
|
|
||||||
self.folder.startswith(os.getcwd())):
|
|
||||||
self.folder = os.path.relpath(self.folder, os.getcwd())
|
|
||||||
|
|
||||||
# Creating the folder if it does not exist
|
|
||||||
if False and self.folder and not exists(self.folder):
|
|
||||||
os.mkdir(self.folder)
|
|
||||||
|
|
||||||
def after_connection_hook(self):
|
|
||||||
"""Hook for the after_connection parameter"""
|
|
||||||
if callable(self._after_connection):
|
|
||||||
self._after_connection(self)
|
|
||||||
self.after_connection()
|
|
||||||
|
|
||||||
def after_connection(self):
|
|
||||||
#this it is supposed to be overloaded by adapters
|
|
||||||
pass
|
|
||||||
|
|
||||||
def reconnect(self, f=None, cursor=True):
|
|
||||||
"""
|
|
||||||
Defines: `self.connection` and `self.cursor`
|
|
||||||
(if cursor is True)
|
|
||||||
if `self.pool_size>0` it will try pull the connection from the pool
|
|
||||||
if the connection is not active (closed by db server) it will loop
|
|
||||||
if not `self.pool_size` or no active connections in pool makes a new one
|
|
||||||
"""
|
|
||||||
if getattr(self,'connection', None) is not None:
|
|
||||||
return
|
|
||||||
if f is None:
|
|
||||||
f = self.connector
|
|
||||||
|
|
||||||
# if not hasattr(self, "driver") or self.driver is None:
|
|
||||||
# LOGGER.debug("Skipping connection since there's no driver")
|
|
||||||
# return
|
|
||||||
|
|
||||||
if not self.pool_size:
|
|
||||||
self.connection = f()
|
|
||||||
self.cursor = cursor and self.connection.cursor()
|
|
||||||
else:
|
|
||||||
uri = self.uri
|
|
||||||
POOLS = ConnectionPool.POOLS
|
|
||||||
while True:
|
|
||||||
GLOBAL_LOCKER.acquire()
|
|
||||||
if not uri in POOLS:
|
|
||||||
POOLS[uri] = []
|
|
||||||
if POOLS[uri]:
|
|
||||||
self.connection = POOLS[uri].pop()
|
|
||||||
GLOBAL_LOCKER.release()
|
|
||||||
self.cursor = cursor and self.connection.cursor()
|
|
||||||
try:
|
|
||||||
if self.cursor and self.check_active_connection:
|
|
||||||
self.execute('SELECT 1;')
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
GLOBAL_LOCKER.release()
|
|
||||||
self.connection = f()
|
|
||||||
self.cursor = cursor and self.connection.cursor()
|
|
||||||
break
|
|
||||||
self.after_connection_hook()
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import copy
|
|
||||||
import marshal
|
|
||||||
import struct
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from .._compat import exists, copyreg
|
|
||||||
from .._globals import LOGGER
|
|
||||||
|
|
||||||
|
|
||||||
class Reference(long):
|
|
||||||
|
|
||||||
def __allocate(self):
|
|
||||||
if not self._record:
|
|
||||||
self._record = self._table[long(self)]
|
|
||||||
if not self._record:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Using a recursive select but encountered a broken reference: %s %d"%(self._table, long(self)))
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
if key == 'id':
|
|
||||||
return long(self)
|
|
||||||
if key in self._table:
|
|
||||||
self.__allocate()
|
|
||||||
if self._record:
|
|
||||||
return self._record.get(key,None) # to deal with case self.update_record()
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
return self.__getattr__(key, default)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
if key.startswith('_'):
|
|
||||||
long.__setattr__(self, key, value)
|
|
||||||
return
|
|
||||||
self.__allocate()
|
|
||||||
self._record[key] = value
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if key == 'id':
|
|
||||||
return long(self)
|
|
||||||
self.__allocate()
|
|
||||||
return self._record.get(key, None)
|
|
||||||
|
|
||||||
def __setitem__(self,key,value):
|
|
||||||
self.__allocate()
|
|
||||||
self._record[key] = value
|
|
||||||
|
|
||||||
def Reference_unpickler(data):
|
|
||||||
return marshal.loads(data)
|
|
||||||
|
|
||||||
def Reference_pickler(data):
|
|
||||||
try:
|
|
||||||
marshal_dump = marshal.dumps(long(data))
|
|
||||||
except AttributeError:
|
|
||||||
marshal_dump = 'i%s' % struct.pack('<i', long(data))
|
|
||||||
return (Reference_unpickler, (marshal_dump,))
|
|
||||||
|
|
||||||
copyreg.pickle(Reference, Reference_pickler, Reference_unpickler)
|
|
||||||
|
|
||||||
|
|
||||||
class SQLCallableList(list):
|
|
||||||
def __call__(self):
|
|
||||||
return copy.copy(self)
|
|
||||||
|
|
||||||
|
|
||||||
class SQLALL(object):
|
|
||||||
"""
|
|
||||||
Helper class providing a comma-separated string having all the field names
|
|
||||||
(prefixed by table name and '.')
|
|
||||||
|
|
||||||
normally only called from within gluon.dal
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, table):
|
|
||||||
self._table = table
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return ', '.join([str(field) for field in self._table])
|
|
||||||
|
|
||||||
|
|
||||||
class SQLCustomType(object):
|
|
||||||
"""
|
|
||||||
Allows defining of custom SQL types
|
|
||||||
|
|
||||||
Args:
|
|
||||||
type: the web2py type (default = 'string')
|
|
||||||
native: the backend type
|
|
||||||
encoder: how to encode the value to store it in the backend
|
|
||||||
decoder: how to decode the value retrieved from the backend
|
|
||||||
validator: what validators to use ( default = None, will use the
|
|
||||||
default validator for type)
|
|
||||||
|
|
||||||
Example::
|
|
||||||
Define as:
|
|
||||||
|
|
||||||
decimal = SQLCustomType(
|
|
||||||
type ='double',
|
|
||||||
native ='integer',
|
|
||||||
encoder =(lambda x: int(float(x) * 100)),
|
|
||||||
decoder = (lambda x: Decimal("0.00") + Decimal(str(float(x)/100)) )
|
|
||||||
)
|
|
||||||
|
|
||||||
db.define_table(
|
|
||||||
'example',
|
|
||||||
Field('value', type=decimal)
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
type='string',
|
|
||||||
native=None,
|
|
||||||
encoder=None,
|
|
||||||
decoder=None,
|
|
||||||
validator=None,
|
|
||||||
_class=None,
|
|
||||||
):
|
|
||||||
|
|
||||||
self.type = type
|
|
||||||
self.native = native
|
|
||||||
self.encoder = encoder or (lambda x: x)
|
|
||||||
self.decoder = decoder or (lambda x: x)
|
|
||||||
self.validator = validator
|
|
||||||
self._class = _class or type
|
|
||||||
|
|
||||||
def startswith(self, text=None):
|
|
||||||
try:
|
|
||||||
return self.type.startswith(self, text)
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def endswith(self, text=None):
|
|
||||||
try:
|
|
||||||
return self.type.endswith(self, text)
|
|
||||||
except TypeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __getslice__(self, a=0, b=100):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __getitem__(self, i):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self._class
|
|
||||||
|
|
||||||
|
|
||||||
class RecordUpdater(object):
|
|
||||||
def __init__(self, colset, table, id):
|
|
||||||
self.colset, self.db, self.tablename, self.id = \
|
|
||||||
colset, table._db, table._tablename, id
|
|
||||||
|
|
||||||
def __call__(self, **fields):
|
|
||||||
colset, db, tablename, id = self.colset, self.db, self.tablename, self.id
|
|
||||||
table = db[tablename]
|
|
||||||
newfields = fields or dict(colset)
|
|
||||||
for fieldname in newfields.keys():
|
|
||||||
if not fieldname in table.fields or table[fieldname].type=='id':
|
|
||||||
del newfields[fieldname]
|
|
||||||
table._db(table._id==id,ignore_common_filters=True).update(**newfields)
|
|
||||||
colset.update(newfields)
|
|
||||||
return colset
|
|
||||||
|
|
||||||
class RecordDeleter(object):
|
|
||||||
def __init__(self, table, id):
|
|
||||||
self.db, self.tablename, self.id = table._db, table._tablename, id
|
|
||||||
def __call__(self):
|
|
||||||
return self.db(self.db[self.tablename]._id==self.id).delete()
|
|
||||||
|
|
||||||
|
|
||||||
class MethodAdder(object):
|
|
||||||
def __init__(self,table):
|
|
||||||
self.table = table
|
|
||||||
def __call__(self):
|
|
||||||
return self.register()
|
|
||||||
def __getattr__(self,method_name):
|
|
||||||
return self.register(method_name)
|
|
||||||
def register(self,method_name=None):
|
|
||||||
def _decorated(f):
|
|
||||||
instance = self.table
|
|
||||||
import types
|
|
||||||
method = types.MethodType(f, instance, instance.__class__)
|
|
||||||
name = method_name or f.func_name
|
|
||||||
setattr(instance, name, method)
|
|
||||||
return f
|
|
||||||
return _decorated
|
|
||||||
|
|
||||||
|
|
||||||
class DatabaseStoredFile:
|
|
||||||
|
|
||||||
web2py_filesystems = set()
|
|
||||||
|
|
||||||
def escape(self,obj):
|
|
||||||
return self.db._adapter.escape(obj)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def try_create_web2py_filesystem(db):
|
|
||||||
if not db._uri in DatabaseStoredFile.web2py_filesystems:
|
|
||||||
if db._adapter.dbengine == 'mysql':
|
|
||||||
sql = "CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(255), content LONGTEXT, PRIMARY KEY(path) ) ENGINE=InnoDB;"
|
|
||||||
elif db._adapter.dbengine in ('postgres', 'sqlite'):
|
|
||||||
sql = "CREATE TABLE IF NOT EXISTS web2py_filesystem (path VARCHAR(255), content TEXT, PRIMARY KEY(path));"
|
|
||||||
db.executesql(sql)
|
|
||||||
DatabaseStoredFile.web2py_filesystems.add(db._uri)
|
|
||||||
|
|
||||||
def __init__(self,db,filename,mode):
|
|
||||||
if not db._adapter.dbengine in ('mysql', 'postgres', 'sqlite'):
|
|
||||||
raise RuntimeError("only MySQL/Postgres/SQLite can store metadata .table files in database for now")
|
|
||||||
self.db = db
|
|
||||||
self.filename = filename
|
|
||||||
self.mode = mode
|
|
||||||
DatabaseStoredFile.try_create_web2py_filesystem(db)
|
|
||||||
self.p=0
|
|
||||||
self.data = ''
|
|
||||||
if mode in ('r','rw','a'):
|
|
||||||
query = "SELECT content FROM web2py_filesystem WHERE path='%s'" \
|
|
||||||
% filename
|
|
||||||
rows = self.db.executesql(query)
|
|
||||||
if rows:
|
|
||||||
self.data = rows[0][0]
|
|
||||||
elif exists(filename):
|
|
||||||
datafile = open(filename, 'r')
|
|
||||||
try:
|
|
||||||
self.data = datafile.read()
|
|
||||||
finally:
|
|
||||||
datafile.close()
|
|
||||||
elif mode in ('r','rw'):
|
|
||||||
raise RuntimeError("File %s does not exist" % filename)
|
|
||||||
|
|
||||||
def read(self, bytes):
|
|
||||||
data = self.data[self.p:self.p+bytes]
|
|
||||||
self.p += len(data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def readline(self):
|
|
||||||
i = self.data.find('\n',self.p)+1
|
|
||||||
if i>0:
|
|
||||||
data, self.p = self.data[self.p:i], i
|
|
||||||
else:
|
|
||||||
data, self.p = self.data[self.p:], len(self.data)
|
|
||||||
return data
|
|
||||||
|
|
||||||
def write(self,data):
|
|
||||||
self.data += data
|
|
||||||
|
|
||||||
def close_connection(self):
|
|
||||||
if self.db is not None:
|
|
||||||
self.db.executesql(
|
|
||||||
"DELETE FROM web2py_filesystem WHERE path='%s'" % self.filename)
|
|
||||||
query = "INSERT INTO web2py_filesystem(path,content) VALUES ('%s','%s')"\
|
|
||||||
% (self.filename, self.data.replace("'","''"))
|
|
||||||
self.db.executesql(query)
|
|
||||||
self.db.commit()
|
|
||||||
self.db = None
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.close_connection()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def exists(db, filename):
|
|
||||||
if exists(filename):
|
|
||||||
return True
|
|
||||||
|
|
||||||
DatabaseStoredFile.try_create_web2py_filesystem(db)
|
|
||||||
|
|
||||||
query = "SELECT path FROM web2py_filesystem WHERE path='%s'" % filename
|
|
||||||
try:
|
|
||||||
if db.executesql(query):
|
|
||||||
return True
|
|
||||||
except Exception, e:
|
|
||||||
if not (db._adapter.isOperationalError(e) or
|
|
||||||
db._adapter.isProgrammingError(e)):
|
|
||||||
raise
|
|
||||||
# no web2py_filesystem found?
|
|
||||||
tb = traceback.format_exc()
|
|
||||||
LOGGER.error("Could not retrieve %s\n%s" % (filename, tb))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class UseDatabaseStoredFile:
|
|
||||||
|
|
||||||
def file_exists(self, filename):
|
|
||||||
return DatabaseStoredFile.exists(self.db,filename)
|
|
||||||
|
|
||||||
def file_open(self, filename, mode='rb', lock=True):
|
|
||||||
return DatabaseStoredFile(self.db,filename,mode)
|
|
||||||
|
|
||||||
def file_close(self, fileobj):
|
|
||||||
fileobj.close_connection()
|
|
||||||
|
|
||||||
def file_delete(self,filename):
|
|
||||||
query = "DELETE FROM web2py_filesystem WHERE path='%s'" % filename
|
|
||||||
self.db.executesql(query)
|
|
||||||
self.db.commit()
|
|
||||||
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import uuid
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .regex import REGEX_NOPASSWD, REGEX_UNPACK, REGEX_CONST_STRING, REGEX_W
|
|
||||||
from .classes import SQLCustomType
|
|
||||||
#from ..objects import Field, Table
|
|
||||||
|
|
||||||
|
|
||||||
PLURALIZE_RULES = [
|
|
||||||
(re.compile('child$'), re.compile('child$'), 'children'),
|
|
||||||
(re.compile('oot$'), re.compile('oot$'), 'eet'),
|
|
||||||
(re.compile('ooth$'), re.compile('ooth$'), 'eeth'),
|
|
||||||
(re.compile('l[eo]af$'), re.compile('l([eo])af$'), 'l\\1aves'),
|
|
||||||
(re.compile('sis$'), re.compile('sis$'), 'ses'),
|
|
||||||
(re.compile('man$'), re.compile('man$'), 'men'),
|
|
||||||
(re.compile('ife$'), re.compile('ife$'), 'ives'),
|
|
||||||
(re.compile('eau$'), re.compile('eau$'), 'eaux'),
|
|
||||||
(re.compile('lf$'), re.compile('lf$'), 'lves'),
|
|
||||||
(re.compile('[sxz]$'), re.compile('$'), 'es'),
|
|
||||||
(re.compile('[^aeioudgkprt]h$'), re.compile('$'), 'es'),
|
|
||||||
(re.compile('(qu|[^aeiou])y$'), re.compile('y$'), 'ies'),
|
|
||||||
(re.compile('$'), re.compile('$'), 's'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def pluralize(singular, rules=PLURALIZE_RULES):
|
|
||||||
for line in rules:
|
|
||||||
re_search, re_sub, replace = line
|
|
||||||
plural = re_search.search(singular) and re_sub.sub(replace, singular)
|
|
||||||
if plural: return plural
|
|
||||||
|
|
||||||
def hide_password(uri):
|
|
||||||
if isinstance(uri,(list,tuple)):
|
|
||||||
return [hide_password(item) for item in uri]
|
|
||||||
return REGEX_NOPASSWD.sub('******',uri)
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup(text):
|
|
||||||
"""
|
|
||||||
Validates that the given text is clean: only contains [0-9a-zA-Z_]
|
|
||||||
"""
|
|
||||||
#if not REGEX_ALPHANUMERIC.match(text):
|
|
||||||
# raise SyntaxError('invalid table or field name: %s' % text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def list_represent(x,r=None):
|
|
||||||
return ', '.join(str(y) for y in x or [])
|
|
||||||
|
|
||||||
|
|
||||||
def xorify(orderby):
|
|
||||||
if not orderby:
|
|
||||||
return None
|
|
||||||
orderby2 = orderby[0]
|
|
||||||
for item in orderby[1:]:
|
|
||||||
orderby2 = orderby2 | item
|
|
||||||
return orderby2
|
|
||||||
|
|
||||||
|
|
||||||
def use_common_filters(query):
|
|
||||||
return (query and hasattr(query,'ignore_common_filters') and \
|
|
||||||
not query.ignore_common_filters)
|
|
||||||
|
|
||||||
|
|
||||||
def bar_escape(item):
|
|
||||||
return str(item).replace('|', '||')
|
|
||||||
|
|
||||||
|
|
||||||
def bar_encode(items):
|
|
||||||
return '|%s|' % '|'.join(bar_escape(item) for item in items if str(item).strip())
|
|
||||||
|
|
||||||
|
|
||||||
def bar_decode_integer(value):
|
|
||||||
if not hasattr(value,'split') and hasattr(value,'read'):
|
|
||||||
value = value.read()
|
|
||||||
return [long(x) for x in value.split('|') if x.strip()]
|
|
||||||
|
|
||||||
|
|
||||||
def bar_decode_string(value):
|
|
||||||
return [x.replace('||', '|') for x in
|
|
||||||
REGEX_UNPACK.split(value[1:-1]) if x.strip()]
|
|
||||||
|
|
||||||
|
|
||||||
def archive_record(qset, fs, archive_table, current_record):
|
|
||||||
tablenames = qset.db._adapter.tables(qset.query)
|
|
||||||
if len(tablenames) != 1:
|
|
||||||
raise RuntimeError("cannot update join")
|
|
||||||
for row in qset.select():
|
|
||||||
fields = archive_table._filter_fields(row)
|
|
||||||
fields[current_record] = row.id
|
|
||||||
archive_table.insert(**fields)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def smart_query(fields,text):
|
|
||||||
from ..objects import Field, Table
|
|
||||||
if not isinstance(fields,(list,tuple)):
|
|
||||||
fields = [fields]
|
|
||||||
new_fields = []
|
|
||||||
for field in fields:
|
|
||||||
if isinstance(field,Field):
|
|
||||||
new_fields.append(field)
|
|
||||||
elif isinstance(field,Table):
|
|
||||||
for ofield in field:
|
|
||||||
new_fields.append(ofield)
|
|
||||||
else:
|
|
||||||
raise RuntimeError("fields must be a list of fields")
|
|
||||||
fields = new_fields
|
|
||||||
field_map = {}
|
|
||||||
for field in fields:
|
|
||||||
n = field.name.lower()
|
|
||||||
if not n in field_map:
|
|
||||||
field_map[n] = field
|
|
||||||
n = str(field).lower()
|
|
||||||
if not n in field_map:
|
|
||||||
field_map[n] = field
|
|
||||||
constants = {}
|
|
||||||
i = 0
|
|
||||||
while True:
|
|
||||||
m = REGEX_CONST_STRING.search(text)
|
|
||||||
if not m: break
|
|
||||||
text = text[:m.start()]+('#%i' % i)+text[m.end():]
|
|
||||||
constants[str(i)] = m.group()[1:-1]
|
|
||||||
i+=1
|
|
||||||
text = re.sub('\s+',' ',text).lower()
|
|
||||||
for a,b in [('&','and'),
|
|
||||||
('|','or'),
|
|
||||||
('~','not'),
|
|
||||||
('==','='),
|
|
||||||
('<','<'),
|
|
||||||
('>','>'),
|
|
||||||
('<=','<='),
|
|
||||||
('>=','>='),
|
|
||||||
('<>','!='),
|
|
||||||
('=<','<='),
|
|
||||||
('=>','>='),
|
|
||||||
('=','='),
|
|
||||||
(' less or equal than ','<='),
|
|
||||||
(' greater or equal than ','>='),
|
|
||||||
(' equal or less than ','<='),
|
|
||||||
(' equal or greater than ','>='),
|
|
||||||
(' less or equal ','<='),
|
|
||||||
(' greater or equal ','>='),
|
|
||||||
(' equal or less ','<='),
|
|
||||||
(' equal or greater ','>='),
|
|
||||||
(' not equal to ','!='),
|
|
||||||
(' not equal ','!='),
|
|
||||||
(' equal to ','='),
|
|
||||||
(' equal ','='),
|
|
||||||
(' equals ','='),
|
|
||||||
(' less than ','<'),
|
|
||||||
(' greater than ','>'),
|
|
||||||
(' starts with ','startswith'),
|
|
||||||
(' ends with ','endswith'),
|
|
||||||
(' not in ' , 'notbelongs'),
|
|
||||||
(' in ' , 'belongs'),
|
|
||||||
(' is ','=')]:
|
|
||||||
if a[0]==' ':
|
|
||||||
text = text.replace(' is'+a,' %s ' % b)
|
|
||||||
text = text.replace(a,' %s ' % b)
|
|
||||||
text = re.sub('\s+',' ',text).lower()
|
|
||||||
text = re.sub('(?P<a>[\<\>\!\=])\s+(?P<b>[\<\>\!\=])','\g<a>\g<b>',text)
|
|
||||||
query = field = neg = op = logic = None
|
|
||||||
for item in text.split():
|
|
||||||
if field is None:
|
|
||||||
if item == 'not':
|
|
||||||
neg = True
|
|
||||||
elif not neg and not logic and item in ('and','or'):
|
|
||||||
logic = item
|
|
||||||
elif item in field_map:
|
|
||||||
field = field_map[item]
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Invalid syntax")
|
|
||||||
elif not field is None and op is None:
|
|
||||||
op = item
|
|
||||||
elif not op is None:
|
|
||||||
if item.startswith('#'):
|
|
||||||
if not item[1:] in constants:
|
|
||||||
raise RuntimeError("Invalid syntax")
|
|
||||||
value = constants[item[1:]]
|
|
||||||
else:
|
|
||||||
value = item
|
|
||||||
if field.type in ('text', 'string', 'json'):
|
|
||||||
if op == '=': op = 'like'
|
|
||||||
if op == '=': new_query = field==value
|
|
||||||
elif op == '<': new_query = field<value
|
|
||||||
elif op == '>': new_query = field>value
|
|
||||||
elif op == '<=': new_query = field<=value
|
|
||||||
elif op == '>=': new_query = field>=value
|
|
||||||
elif op == '!=': new_query = field!=value
|
|
||||||
elif op == 'belongs': new_query = field.belongs(value.split(','))
|
|
||||||
elif op == 'notbelongs': new_query = ~field.belongs(value.split(','))
|
|
||||||
elif field.type in ('text', 'string', 'json'):
|
|
||||||
if op == 'contains': new_query = field.contains(value)
|
|
||||||
elif op == 'like': new_query = field.ilike(value)
|
|
||||||
elif op == 'startswith': new_query = field.startswith(value)
|
|
||||||
elif op == 'endswith': new_query = field.endswith(value)
|
|
||||||
else: raise RuntimeError("Invalid operation")
|
|
||||||
elif field._db._adapter.dbengine=='google:datastore' and \
|
|
||||||
field.type in ('list:integer', 'list:string', 'list:reference'):
|
|
||||||
if op == 'contains': new_query = field.contains(value)
|
|
||||||
else: raise RuntimeError("Invalid operation")
|
|
||||||
else: raise RuntimeError("Invalid operation")
|
|
||||||
if neg: new_query = ~new_query
|
|
||||||
if query is None:
|
|
||||||
query = new_query
|
|
||||||
elif logic == 'and':
|
|
||||||
query &= new_query
|
|
||||||
elif logic == 'or':
|
|
||||||
query |= new_query
|
|
||||||
field = op = neg = logic = None
|
|
||||||
return query
|
|
||||||
|
|
||||||
|
|
||||||
def sqlhtml_validators(field):
|
|
||||||
"""
|
|
||||||
Field type validation, using web2py's validators mechanism.
|
|
||||||
|
|
||||||
makes sure the content of a field is in line with the declared
|
|
||||||
fieldtype
|
|
||||||
"""
|
|
||||||
db = field.db
|
|
||||||
try:
|
|
||||||
from gluon import validators
|
|
||||||
except ImportError:
|
|
||||||
return []
|
|
||||||
field_type, field_length = field.type, field.length
|
|
||||||
if isinstance(field_type, SQLCustomType):
|
|
||||||
if hasattr(field_type, 'validator'):
|
|
||||||
return field_type.validator
|
|
||||||
else:
|
|
||||||
field_type = field_type.type
|
|
||||||
elif not isinstance(field_type,str):
|
|
||||||
return []
|
|
||||||
requires=[]
|
|
||||||
def ff(r,id):
|
|
||||||
row=r(id)
|
|
||||||
if not row:
|
|
||||||
return str(id)
|
|
||||||
elif hasattr(r, '_format') and isinstance(r._format,str):
|
|
||||||
return r._format % row
|
|
||||||
elif hasattr(r, '_format') and callable(r._format):
|
|
||||||
return r._format(row)
|
|
||||||
else:
|
|
||||||
return str(id)
|
|
||||||
if field_type in (('string', 'text', 'password')):
|
|
||||||
requires.append(validators.IS_LENGTH(field_length))
|
|
||||||
elif field_type == 'json':
|
|
||||||
requires.append(validators.IS_EMPTY_OR(validators.IS_JSON()))
|
|
||||||
elif field_type == 'double' or field_type == 'float':
|
|
||||||
requires.append(validators.IS_FLOAT_IN_RANGE(-1e100, 1e100))
|
|
||||||
elif field_type == 'integer':
|
|
||||||
requires.append(validators.IS_INT_IN_RANGE(-2**31, 2**31))
|
|
||||||
elif field_type == 'bigint':
|
|
||||||
requires.append(validators.IS_INT_IN_RANGE(-2**63, 2**63))
|
|
||||||
elif field_type.startswith('decimal'):
|
|
||||||
requires.append(validators.IS_DECIMAL_IN_RANGE(-10**10, 10**10))
|
|
||||||
elif field_type == 'date':
|
|
||||||
requires.append(validators.IS_DATE())
|
|
||||||
elif field_type == 'time':
|
|
||||||
requires.append(validators.IS_TIME())
|
|
||||||
elif field_type == 'datetime':
|
|
||||||
requires.append(validators.IS_DATETIME())
|
|
||||||
elif db and field_type.startswith('reference') and \
|
|
||||||
field_type.find('.') < 0 and \
|
|
||||||
field_type[10:] in db.tables:
|
|
||||||
referenced = db[field_type[10:]]
|
|
||||||
def repr_ref(id, row=None, r=referenced, f=ff): return f(r, id)
|
|
||||||
field.represent = field.represent or repr_ref
|
|
||||||
if hasattr(referenced, '_format') and referenced._format:
|
|
||||||
requires = validators.IS_IN_DB(db,referenced._id,
|
|
||||||
referenced._format)
|
|
||||||
if field.unique:
|
|
||||||
requires._and = validators.IS_NOT_IN_DB(db,field)
|
|
||||||
if field.tablename == field_type[10:]:
|
|
||||||
return validators.IS_EMPTY_OR(requires)
|
|
||||||
return requires
|
|
||||||
elif db and field_type.startswith('list:reference') and \
|
|
||||||
field_type.find('.') < 0 and \
|
|
||||||
field_type[15:] in db.tables:
|
|
||||||
referenced = db[field_type[15:]]
|
|
||||||
def list_ref_repr(ids, row=None, r=referenced, f=ff):
|
|
||||||
if not ids:
|
|
||||||
return None
|
|
||||||
from ..adapters.google import GoogleDatastoreAdapter
|
|
||||||
refs = None
|
|
||||||
db, id = r._db, r._id
|
|
||||||
if isinstance(db._adapter, GoogleDatastoreAdapter):
|
|
||||||
def count(values): return db(id.belongs(values)).select(id)
|
|
||||||
rx = range(0, len(ids), 30)
|
|
||||||
refs = reduce(lambda a,b:a&b, [count(ids[i:i+30]) for i in rx])
|
|
||||||
else:
|
|
||||||
refs = db(id.belongs(ids)).select(id)
|
|
||||||
return (refs and ', '.join(f(r,x.id) for x in refs) or '')
|
|
||||||
field.represent = field.represent or list_ref_repr
|
|
||||||
if hasattr(referenced, '_format') and referenced._format:
|
|
||||||
requires = validators.IS_IN_DB(db,referenced._id,
|
|
||||||
referenced._format,multiple=True)
|
|
||||||
else:
|
|
||||||
requires = validators.IS_IN_DB(db,referenced._id,
|
|
||||||
multiple=True)
|
|
||||||
if field.unique:
|
|
||||||
requires._and = validators.IS_NOT_IN_DB(db,field)
|
|
||||||
if not field.notnull:
|
|
||||||
requires = validators.IS_EMPTY_OR(requires)
|
|
||||||
return requires
|
|
||||||
elif field_type.startswith('list:'):
|
|
||||||
def repr_list(values,row=None): return', '.join(str(v) for v in (values or []))
|
|
||||||
field.represent = field.represent or repr_list
|
|
||||||
if field.unique:
|
|
||||||
requires.append(validators.IS_NOT_IN_DB(db, field))
|
|
||||||
sff = ['in', 'do', 'da', 'ti', 'de', 'bo']
|
|
||||||
if field.notnull and not field_type[:2] in sff:
|
|
||||||
requires.append(validators.IS_NOT_EMPTY())
|
|
||||||
elif not field.notnull and field_type[:2] in sff and requires:
|
|
||||||
requires[0] = validators.IS_EMPTY_OR(requires[0])
|
|
||||||
return requires
|
|
||||||
|
|
||||||
|
|
||||||
def varquote_aux(name,quotestr='%s'):
|
|
||||||
return name if REGEX_W.match(name) else quotestr % name
|
|
||||||
|
|
||||||
|
|
||||||
def uuid2int(uuidv):
|
|
||||||
return uuid.UUID(uuidv).int
|
|
||||||
|
|
||||||
|
|
||||||
def int2uuid(n):
|
|
||||||
return str(uuid.UUID(int=n))
|
|
||||||
|
|
||||||
|
|
||||||
# Geodal utils
|
|
||||||
def geoPoint(x, y):
|
|
||||||
return "POINT (%f %f)" % (x, y)
|
|
||||||
|
|
||||||
|
|
||||||
def geoLine(*line):
|
|
||||||
return "LINESTRING (%s)" % ','.join("%f %f" % item for item in line)
|
|
||||||
|
|
||||||
|
|
||||||
def geoPolygon(*line):
|
|
||||||
return "POLYGON ((%s))" % ','.join("%f %f" % item for item in line)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
REGEX_TYPE = re.compile('^([\w\_\:]+)')
|
|
||||||
REGEX_DBNAME = re.compile('^(\w+)(\:\w+)*')
|
|
||||||
REGEX_W = re.compile('^\w+$')
|
|
||||||
REGEX_TABLE_DOT_FIELD = re.compile('^(\w+)\.([^.]+)$')
|
|
||||||
REGEX_NO_GREEDY_ENTITY_NAME = r'(.+?)'
|
|
||||||
REGEX_UPLOAD_PATTERN = re.compile('(?P<table>[\w\-]+)\.(?P<field>[\w\-]+)\.(?P<uuidkey>[\w\-]+)(\.(?P<name>\w+))?\.\w+$')
|
|
||||||
REGEX_CLEANUP_FN = re.compile('[\'"\s;]+')
|
|
||||||
REGEX_UNPACK = re.compile('(?<!\|)\|(?!\|)')
|
|
||||||
REGEX_PYTHON_KEYWORDS = re.compile('^(and|del|from|not|while|as|elif|global|or|with|assert|else|if|pass|yield|break|except|import|print|class|exec|in|raise|continue|finally|is|return|def|for|lambda|try)$')
|
|
||||||
REGEX_SELECT_AS_PARSER = re.compile("\s+AS\s+(\S+)")
|
|
||||||
REGEX_CONST_STRING = re.compile('(\"[^\"]*?\")|(\'[^\']*?\')')
|
|
||||||
REGEX_SEARCH_PATTERN = re.compile('^{[^\.]+\.[^\.]+(\.(lt|gt|le|ge|eq|ne|contains|startswith|year|month|day|hour|minute|second))?(\.not)?}$')
|
|
||||||
REGEX_SQUARE_BRACKETS = re.compile('^.+\[.+\]$')
|
|
||||||
REGEX_STORE_PATTERN = re.compile('\.(?P<e>\w{1,5})$')
|
|
||||||
REGEX_QUOTES = re.compile("'[^']*'")
|
|
||||||
REGEX_ALPHANUMERIC = re.compile('^[0-9a-zA-Z]\w*$')
|
|
||||||
REGEX_PASSWORD = re.compile('\://([^:@]*)\:')
|
|
||||||
REGEX_NOPASSWD = re.compile('\/\/[\w\.\-]+[\:\/](.+)(?=@)') # was '(?<=[\:\/])([^:@/]+)(?=@.+)'
|
|
||||||
File diff suppressed because it is too large
Load Diff
+6
-1
@@ -25,7 +25,6 @@ from gluon.serializers import json, custom_json
|
|||||||
import gluon.settings as settings
|
import gluon.settings as settings
|
||||||
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
from gluon.utils import web2py_uuid, secure_dumps, secure_loads
|
||||||
from gluon.settings import global_settings
|
from gluon.settings import global_settings
|
||||||
from gluon.dal import Field
|
|
||||||
from gluon import recfile
|
from gluon import recfile
|
||||||
import hashlib
|
import hashlib
|
||||||
import portalocker
|
import portalocker
|
||||||
@@ -598,6 +597,7 @@ class Response(Storage):
|
|||||||
|
|
||||||
Downloads from http://..../download/filename
|
Downloads from http://..../download/filename
|
||||||
"""
|
"""
|
||||||
|
from pydal.exceptions import NotAuthorizedException, NotFoundException
|
||||||
|
|
||||||
current.session.forget(current.response)
|
current.session.forget(current.response)
|
||||||
|
|
||||||
@@ -614,6 +614,10 @@ class Response(Storage):
|
|||||||
raise HTTP(404)
|
raise HTTP(404)
|
||||||
try:
|
try:
|
||||||
(filename, stream) = field.retrieve(name, nameonly=True)
|
(filename, stream) = field.retrieve(name, nameonly=True)
|
||||||
|
except NotAuthorizedException:
|
||||||
|
raise HTTP(403)
|
||||||
|
except NotFoundException:
|
||||||
|
raise HTTP(404)
|
||||||
except IOError:
|
except IOError:
|
||||||
raise HTTP(404)
|
raise HTTP(404)
|
||||||
headers = self.headers
|
headers = self.headers
|
||||||
@@ -769,6 +773,7 @@ class Session(Storage):
|
|||||||
compression_level(int): 0-9, sets zlib compression on the data
|
compression_level(int): 0-9, sets zlib compression on the data
|
||||||
before the encryption
|
before the encryption
|
||||||
"""
|
"""
|
||||||
|
from gluon.dal import Field
|
||||||
request = request or current.request
|
request = request or current.request
|
||||||
response = response or current.response
|
response = response or current.response
|
||||||
masterapp = masterapp or request.application
|
masterapp = masterapp or request.application
|
||||||
|
|||||||
+1
-1
@@ -850,7 +850,7 @@ class DIV(XmlComponent):
|
|||||||
"""
|
"""
|
||||||
components = []
|
components = []
|
||||||
for c in self.components:
|
for c in self.components:
|
||||||
if isinstance(c, allowed_parents):
|
if isinstance(c, (allowed_parents,CAT)):
|
||||||
pass
|
pass
|
||||||
elif wrap_lambda:
|
elif wrap_lambda:
|
||||||
c = wrap_lambda(c)
|
c = wrap_lambda(c)
|
||||||
|
|||||||
+1
-1
@@ -93,7 +93,7 @@ from gluon.globals import Request, Response, Session
|
|||||||
from gluon.compileapp import build_environment, run_models_in, \
|
from gluon.compileapp import build_environment, run_models_in, \
|
||||||
run_controller_in, run_view_in
|
run_controller_in, run_view_in
|
||||||
from gluon.contenttype import contenttype
|
from gluon.contenttype import contenttype
|
||||||
from gluon.dal.base import BaseAdapter
|
from pydal.base import BaseAdapter
|
||||||
from gluon.validators import CRYPT
|
from gluon.validators import CRYPT
|
||||||
from gluon.html import URL, xmlescape
|
from gluon.html import URL, xmlescape
|
||||||
from gluon.utils import is_valid_ip_address, getipaddrinfo
|
from gluon.utils import is_valid_ip_address, getipaddrinfo
|
||||||
|
|||||||
Submodule
+1
Submodule gluon/packages/dal added at b533d6a7a8
+2
-2
@@ -609,7 +609,7 @@ class Scheduler(MetaScheduler):
|
|||||||
|
|
||||||
def define_tables(self, db, migrate):
|
def define_tables(self, db, migrate):
|
||||||
"""Defines Scheduler tables structure"""
|
"""Defines Scheduler tables structure"""
|
||||||
from gluon.dal.base import DEFAULT
|
from pydal.base import DEFAULT
|
||||||
logger.debug('defining tables (migrate=%s)', migrate)
|
logger.debug('defining tables (migrate=%s)', migrate)
|
||||||
now = self.now
|
now = self.now
|
||||||
db.define_table(
|
db.define_table(
|
||||||
@@ -1330,7 +1330,7 @@ class Scheduler(MetaScheduler):
|
|||||||
have all fields == None
|
have all fields == None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from gluon.dal.objects import Query
|
from pydal.objects import Query
|
||||||
sr, st = self.db.scheduler_run, self.db.scheduler_task
|
sr, st = self.db.scheduler_run, self.db.scheduler_task
|
||||||
if isinstance(ref, (int, long)):
|
if isinstance(ref, (int, long)):
|
||||||
q = st.id == ref
|
q = st.id == ref
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ from gluon.restricted import RestrictedError
|
|||||||
from gluon.globals import Request, Response, Session
|
from gluon.globals import Request, Response, Session
|
||||||
from gluon.storage import Storage, List
|
from gluon.storage import Storage, List
|
||||||
from gluon.admin import w2p_unpack
|
from gluon.admin import w2p_unpack
|
||||||
from gluon.dal.base import BaseAdapter
|
from pydal.base import BaseAdapter
|
||||||
|
|
||||||
logger = logging.getLogger("web2py")
|
logger = logging.getLogger("web2py")
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -13,9 +13,10 @@ Just for backward compatibility
|
|||||||
__all__ = ['DAL', 'Field', 'DRIVERS']
|
__all__ = ['DAL', 'Field', 'DRIVERS']
|
||||||
|
|
||||||
from dal import DAL, Field, SQLCustomType
|
from dal import DAL, Field, SQLCustomType
|
||||||
from dal.adapters.base import BaseAdapter, DRIVERS
|
from pydal.base import BaseAdapter
|
||||||
from dal.objects import Table, Query, Set, Expression, Row, Rows
|
from pydal.drivers import DRIVERS
|
||||||
from dal.helpers.classes import SQLALL
|
from pydal.objects import Table, Query, Set, Expression, Row, Rows
|
||||||
|
from pydal.helpers.classes import SQLALL
|
||||||
|
|
||||||
SQLDB = DAL
|
SQLDB = DAL
|
||||||
GQLDB = DAL
|
GQLDB = DAL
|
||||||
|
|||||||
+23
-14
@@ -26,12 +26,12 @@ from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
|
|||||||
from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
|
from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
|
||||||
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
|
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
|
||||||
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
|
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
|
||||||
from gluon.dal import DAL, Field
|
from pydal.base import DEFAULT
|
||||||
from gluon.dal.base import DEFAULT
|
from pydal.objects import Table, Row, Expression, Field
|
||||||
from gluon.dal.objects import Table, Row, Expression
|
from pydal.adapters.base import CALLABLETYPES
|
||||||
from gluon.dal.adapters.base import CALLABLETYPES
|
from pydal.helpers.methods import smart_query, bar_encode
|
||||||
from gluon.dal.helpers.methods import smart_query, bar_encode, sqlhtml_validators
|
from pydal.helpers.classes import Reference, SQLCustomType
|
||||||
from gluon.dal.helpers.classes import Reference, SQLCustomType
|
from gluon.dal import _default_validators
|
||||||
from gluon.storage import Storage
|
from gluon.storage import Storage
|
||||||
from gluon.utils import md5_hash
|
from gluon.utils import md5_hash
|
||||||
from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE
|
from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE
|
||||||
@@ -1127,7 +1127,8 @@ class SQLFORM(FORM):
|
|||||||
extra_field.table = table
|
extra_field.table = table
|
||||||
extra_field.tablename = table._tablename
|
extra_field.tablename = table._tablename
|
||||||
if extra_field.requires == DEFAULT:
|
if extra_field.requires == DEFAULT:
|
||||||
extra_field.requires = sqlhtml_validators(extra_field)
|
extra_field.requires = _default_validators(table._db,
|
||||||
|
extra_field)
|
||||||
|
|
||||||
for fieldname in self.fields:
|
for fieldname in self.fields:
|
||||||
if fieldname.find('.') >= 0:
|
if fieldname.find('.') >= 0:
|
||||||
@@ -1722,6 +1723,8 @@ class SQLFORM(FORM):
|
|||||||
Internally will build a non-database based data model
|
Internally will build a non-database based data model
|
||||||
to hold the fields.
|
to hold the fields.
|
||||||
"""
|
"""
|
||||||
|
# this is here to avoid circular references
|
||||||
|
from gluon.dal import DAL
|
||||||
# Define a table name, this way it can be logical to our CSS.
|
# Define a table name, this way it can be logical to our CSS.
|
||||||
# And if you switch from using SQLFORM to SQLFORM.factory
|
# And if you switch from using SQLFORM to SQLFORM.factory
|
||||||
# your same css definitions will still apply.
|
# your same css definitions will still apply.
|
||||||
@@ -2332,8 +2335,12 @@ class SQLFORM(FORM):
|
|||||||
#fields but not virtual fields
|
#fields but not virtual fields
|
||||||
sfields = reduce(lambda a, b: a + b,
|
sfields = reduce(lambda a, b: a + b,
|
||||||
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
|
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
|
||||||
dbset = dbset(SQLFORM.build_query(
|
#use custom_query using searchable
|
||||||
sfields, keywords))
|
if callable(searchable):
|
||||||
|
dbset = dbset(searchable(sfields, keywords))
|
||||||
|
else:
|
||||||
|
dbset = dbset(SQLFORM.build_query(
|
||||||
|
sfields, keywords))
|
||||||
rows = dbset.select(left=left, orderby=orderby,
|
rows = dbset.select(left=left, orderby=orderby,
|
||||||
cacheable=True, *selectable_columns)
|
cacheable=True, *selectable_columns)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@@ -2348,7 +2355,9 @@ class SQLFORM(FORM):
|
|||||||
# expcolumns is all cols to be exported including virtual fields
|
# expcolumns is all cols to be exported including virtual fields
|
||||||
rows.colnames = expcolumns
|
rows.colnames = expcolumns
|
||||||
oExp = clazz(rows)
|
oExp = clazz(rows)
|
||||||
filename = '.'.join(('rows', oExp.file_ext))
|
export_filename = \
|
||||||
|
request.vars.get('_export_filename') or 'rows'
|
||||||
|
filename = '.'.join((export_filename, oExp.file_ext))
|
||||||
response.headers['Content-Type'] = oExp.content_type
|
response.headers['Content-Type'] = oExp.content_type
|
||||||
response.headers['Content-Disposition'] = \
|
response.headers['Content-Disposition'] = \
|
||||||
'attachment;filename=' + filename + ';'
|
'attachment;filename=' + filename + ';'
|
||||||
@@ -3190,13 +3199,13 @@ class SQLTABLE(TABLE):
|
|||||||
r = ''
|
r = ''
|
||||||
elif field.type in ['string', 'text']:
|
elif field.type in ['string', 'text']:
|
||||||
r = str(field.formatter(r))
|
r = str(field.formatter(r))
|
||||||
|
truncate_by = truncate
|
||||||
if headers != {}: # new implement dict
|
if headers != {}: # new implement dict
|
||||||
if isinstance(headers[colname], dict):
|
if isinstance(headers[colname], dict):
|
||||||
if isinstance(headers[colname]['truncate'], int):
|
if isinstance(headers[colname]['truncate'], int):
|
||||||
r = truncate_string(
|
truncate_by = headers[colname]['truncate']
|
||||||
r, headers[colname]['truncate'])
|
if not truncate_by is None:
|
||||||
elif not truncate is None:
|
r = truncate_string(r, truncate_by)
|
||||||
r = truncate_string(r, truncate)
|
|
||||||
attrcol = dict() # new implement dict
|
attrcol = dict() # new implement dict
|
||||||
if headers != {}:
|
if headers != {}:
|
||||||
if isinstance(headers[colname], dict):
|
if isinstance(headers[colname], dict):
|
||||||
|
|||||||
+1
-1
@@ -195,7 +195,7 @@ class Messages(Settings):
|
|||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
value = self[key]
|
value = self[key]
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return str(self.T(value))
|
return self.T(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
class FastStorage(dict):
|
class FastStorage(dict):
|
||||||
|
|||||||
+3
-11
@@ -1,4 +1,4 @@
|
|||||||
import os, sys
|
import sys
|
||||||
|
|
||||||
from test_http import *
|
from test_http import *
|
||||||
from test_cache import *
|
from test_cache import *
|
||||||
@@ -16,16 +16,8 @@ from test_validators import *
|
|||||||
from test_utils import *
|
from test_utils import *
|
||||||
from test_contribs import *
|
from test_contribs import *
|
||||||
from test_web import *
|
from test_web import *
|
||||||
|
from test_dal import *
|
||||||
|
from test_tools import *
|
||||||
|
|
||||||
if sys.version[:3] == '2.7':
|
if sys.version[:3] == '2.7':
|
||||||
from test_old_doctests import *
|
from test_old_doctests import *
|
||||||
|
|
||||||
|
|
||||||
NOSQL = any([name in (os.getenv("DB") or "")
|
|
||||||
for name in ("datastore", "mongodb", "imap")])
|
|
||||||
|
|
||||||
if NOSQL:
|
|
||||||
from test_dal_nosql import *
|
|
||||||
else:
|
|
||||||
from test_dal import *
|
|
||||||
+12
-1650
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Unit tests for gluon.tools
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
if sys.version < "2.7":
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from fix_path import fix_sys_path
|
||||||
|
|
||||||
|
fix_sys_path(__file__)
|
||||||
|
|
||||||
|
DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
|
||||||
|
|
||||||
|
from gluon.dal import DAL, Field
|
||||||
|
from pydal.objects import Table
|
||||||
|
from tools import Auth
|
||||||
|
from gluon.globals import Request, Response, Session
|
||||||
|
from storage import Storage
|
||||||
|
from languages import translator
|
||||||
|
from gluon.http import HTTP
|
||||||
|
|
||||||
|
python_version = sys.version[:3]
|
||||||
|
IS_IMAP = "imap" in DEFAULT_URI
|
||||||
|
|
||||||
|
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
|
||||||
|
class testAuth(unittest.TestCase):
|
||||||
|
|
||||||
|
def testRun(self):
|
||||||
|
# setup
|
||||||
|
request = Request(env={})
|
||||||
|
request.application = 'a'
|
||||||
|
request.controller = 'c'
|
||||||
|
request.function = 'f'
|
||||||
|
request.folder = 'applications/admin'
|
||||||
|
response = Response()
|
||||||
|
session = Session()
|
||||||
|
T = translator('', 'en')
|
||||||
|
session.connect(request, response)
|
||||||
|
from gluon.globals import current
|
||||||
|
current.request = request
|
||||||
|
current.response = response
|
||||||
|
current.session = session
|
||||||
|
current.T = T
|
||||||
|
db = DAL(DEFAULT_URI, check_reserved=['all'])
|
||||||
|
auth = Auth(db)
|
||||||
|
auth.define_tables(username=True, signature=False)
|
||||||
|
self.assertTrue('auth_user' in db)
|
||||||
|
self.assertTrue('auth_group' in db)
|
||||||
|
self.assertTrue('auth_membership' in db)
|
||||||
|
self.assertTrue('auth_permission' in db)
|
||||||
|
self.assertTrue('auth_event' in db)
|
||||||
|
db.define_table('t0', Field('tt'), auth.signature)
|
||||||
|
auth.enable_record_versioning(db)
|
||||||
|
self.assertTrue('t0_archive' in db)
|
||||||
|
for f in ['login', 'register', 'retrieve_password',
|
||||||
|
'retrieve_username']:
|
||||||
|
html_form = getattr(auth, f)().xml()
|
||||||
|
self.assertTrue('name="_formkey"' in html_form)
|
||||||
|
|
||||||
|
for f in ['logout', 'verify_email', 'reset_password',
|
||||||
|
'change_password', 'profile', 'groups']:
|
||||||
|
self.assertRaisesRegexp(HTTP, "303*", getattr(auth, f))
|
||||||
|
|
||||||
|
self.assertRaisesRegexp(HTTP, "401*", auth.impersonate)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for t in ['t0_archive', 't0', 'auth_cas', 'auth_event',
|
||||||
|
'auth_membership', 'auth_permission', 'auth_group',
|
||||||
|
'auth_user']:
|
||||||
|
db[t].drop()
|
||||||
|
except SyntaxError as e:
|
||||||
|
# GAE doesn't support drop
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
+15
-6
@@ -43,7 +43,7 @@ from gluon import *
|
|||||||
from gluon.contrib.autolinks import expand_one
|
from gluon.contrib.autolinks import expand_one
|
||||||
from gluon.contrib.markmin.markmin2html import \
|
from gluon.contrib.markmin.markmin2html import \
|
||||||
replace_at_urls, replace_autolinks, replace_components
|
replace_at_urls, replace_autolinks, replace_components
|
||||||
from gluon.dal.objects import Table, Row, Set, Query
|
from pydal.objects import Table, Row, Set, Query
|
||||||
|
|
||||||
import gluon.serializers as serializers
|
import gluon.serializers as serializers
|
||||||
|
|
||||||
@@ -136,6 +136,7 @@ class Mail(object):
|
|||||||
mail.settings.server
|
mail.settings.server
|
||||||
mail.settings.sender
|
mail.settings.sender
|
||||||
mail.settings.login
|
mail.settings.login
|
||||||
|
mail.settings.timeout = 60 # seconds (default)
|
||||||
|
|
||||||
When server is 'logging', email is logged but not sent (debug mode)
|
When server is 'logging', email is logged but not sent (debug mode)
|
||||||
|
|
||||||
@@ -265,6 +266,7 @@ class Mail(object):
|
|||||||
settings.sender = sender
|
settings.sender = sender
|
||||||
settings.login = login
|
settings.login = login
|
||||||
settings.tls = tls
|
settings.tls = tls
|
||||||
|
settings.timeout = 60 # seconds
|
||||||
settings.hostname = None
|
settings.hostname = None
|
||||||
settings.ssl = False
|
settings.ssl = False
|
||||||
settings.cipher_type = None
|
settings.cipher_type = None
|
||||||
@@ -787,10 +789,11 @@ class Mail(object):
|
|||||||
subject=subject, body=text, **xcc)
|
subject=subject, body=text, **xcc)
|
||||||
else:
|
else:
|
||||||
smtp_args = self.settings.server.split(':')
|
smtp_args = self.settings.server.split(':')
|
||||||
|
kwargs = dict(timeout = self.settings.timeout)
|
||||||
if self.settings.ssl:
|
if self.settings.ssl:
|
||||||
server = smtplib.SMTP_SSL(*smtp_args)
|
server = smtplib.SMTP_SSL(*smtp_args, **kwargs)
|
||||||
else:
|
else:
|
||||||
server = smtplib.SMTP(*smtp_args)
|
server = smtplib.SMTP(*smtp_args, **kwargs)
|
||||||
if self.settings.tls and not self.settings.ssl:
|
if self.settings.tls and not self.settings.ssl:
|
||||||
server.ehlo(self.settings.hostname)
|
server.ehlo(self.settings.hostname)
|
||||||
server.starttls()
|
server.starttls()
|
||||||
@@ -842,6 +845,7 @@ class Recaptcha(DIV):
|
|||||||
comment = '',
|
comment = '',
|
||||||
ajax=False
|
ajax=False
|
||||||
):
|
):
|
||||||
|
request = request or current.request
|
||||||
self.request_vars = request and request.vars or current.request.vars
|
self.request_vars = request and request.vars or current.request.vars
|
||||||
self.remote_addr = request.env.remote_addr
|
self.remote_addr = request.env.remote_addr
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
@@ -1257,7 +1261,8 @@ class Auth(object):
|
|||||||
def __init__(self, environment=None, db=None, mailer=True,
|
def __init__(self, environment=None, db=None, mailer=True,
|
||||||
hmac_key=None, controller='default', function='user',
|
hmac_key=None, controller='default', function='user',
|
||||||
cas_provider=None, signature=True, secure=False,
|
cas_provider=None, signature=True, secure=False,
|
||||||
csrf_prevention=True, propagate_extension=None):
|
csrf_prevention=True, propagate_extension=None,
|
||||||
|
url_index=None):
|
||||||
|
|
||||||
## next two lines for backward compatibility
|
## next two lines for backward compatibility
|
||||||
if not db and environment and isinstance(environment, DAL):
|
if not db and environment and isinstance(environment, DAL):
|
||||||
@@ -1294,7 +1299,7 @@ class Auth(object):
|
|||||||
del session.auth
|
del session.auth
|
||||||
# ## what happens after login?
|
# ## what happens after login?
|
||||||
|
|
||||||
url_index = URL(controller, 'index')
|
url_index = url_index or URL(controller, 'index')
|
||||||
url_login = URL(controller, function, args='login',
|
url_login = URL(controller, function, args='login',
|
||||||
extension = propagate_extension)
|
extension = propagate_extension)
|
||||||
# ## what happens after registration?
|
# ## what happens after registration?
|
||||||
@@ -3092,10 +3097,15 @@ class Auth(object):
|
|||||||
IS_EMAIL(error_message=self.messages.invalid_email),
|
IS_EMAIL(error_message=self.messages.invalid_email),
|
||||||
IS_IN_DB(self.db, table_user.email,
|
IS_IN_DB(self.db, table_user.email,
|
||||||
error_message=self.messages.invalid_email)]
|
error_message=self.messages.invalid_email)]
|
||||||
|
if not self.settings.email_case_sensitive:
|
||||||
|
table_user.email.requires.insert(0, IS_LOWER())
|
||||||
else:
|
else:
|
||||||
table_user.username.requires = [
|
table_user.username.requires = [
|
||||||
IS_IN_DB(self.db, table_user.username,
|
IS_IN_DB(self.db, table_user.username,
|
||||||
error_message=self.messages.invalid_username)]
|
error_message=self.messages.invalid_username)]
|
||||||
|
if not self.settings.username_case_sensitive:
|
||||||
|
table_user.username.requires.insert(0, IS_LOWER())
|
||||||
|
|
||||||
form = SQLFORM(table_user,
|
form = SQLFORM(table_user,
|
||||||
fields=[userfield],
|
fields=[userfield],
|
||||||
hidden=dict(_next=next),
|
hidden=dict(_next=next),
|
||||||
@@ -6164,4 +6174,3 @@ class Config(object):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|
||||||
|
|||||||
@@ -350,4 +350,3 @@ def getipaddrinfo(host):
|
|||||||
and isinstance(addrinfo[4][0], basestring)]
|
and isinstance(addrinfo[4][0], basestring)]
|
||||||
except socket.error:
|
except socket.error:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -22,7 +22,7 @@ import decimal
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
||||||
from gluon.dal.objects import FieldVirtual, FieldMethod
|
from pydal.objects import FieldVirtual, FieldMethod
|
||||||
|
|
||||||
regex_isint = re.compile('^[+-]?\d+$')
|
regex_isint = re.compile('^[+-]?\d+$')
|
||||||
|
|
||||||
@@ -506,7 +506,7 @@ class IS_IN_DB(Validator):
|
|||||||
sort=False,
|
sort=False,
|
||||||
_and=None,
|
_and=None,
|
||||||
):
|
):
|
||||||
from dal.objects import Table
|
from pydal.objects import Table
|
||||||
if isinstance(field, Table):
|
if isinstance(field, Table):
|
||||||
field = field._id
|
field = field._id
|
||||||
|
|
||||||
@@ -603,7 +603,7 @@ class IS_IN_DB(Validator):
|
|||||||
if not [v for v in values if not v in self.theset]:
|
if not [v for v in values if not v in self.theset]:
|
||||||
return (values, None)
|
return (values, None)
|
||||||
else:
|
else:
|
||||||
from dal.adapters import GoogleDatastoreAdapter
|
from pydal.adapters import GoogleDatastoreAdapter
|
||||||
|
|
||||||
def count(values, s=self.dbset, f=field):
|
def count(values, s=self.dbset, f=field):
|
||||||
return s(f.belongs(map(int, values))).count()
|
return s(f.belongs(map(int, values))).count()
|
||||||
@@ -648,7 +648,7 @@ class IS_NOT_IN_DB(Validator):
|
|||||||
ignore_common_filters=False,
|
ignore_common_filters=False,
|
||||||
):
|
):
|
||||||
|
|
||||||
from dal.objects import Table
|
from pydal.objects import Table
|
||||||
if isinstance(field, Table):
|
if isinstance(field, Table):
|
||||||
field = field._id
|
field = field._id
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1086,7 +1086,7 @@ def start(cron=True):
|
|||||||
print ProgramAuthor
|
print ProgramAuthor
|
||||||
print ProgramVersion
|
print ProgramVersion
|
||||||
|
|
||||||
from dal.adapters.base import DRIVERS
|
from pydal.drivers import DRIVERS
|
||||||
if not options.nobanner:
|
if not options.nobanner:
|
||||||
print 'Database drivers available: %s' % ', '.join(DRIVERS)
|
print 'Database drivers available: %s' % ', '.join(DRIVERS)
|
||||||
|
|
||||||
|
|||||||
@@ -110,4 +110,3 @@ def main():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,11 @@ def read_file(filename):
|
|||||||
finally:
|
finally:
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
file = sys.argv[1]
|
for file in sys.argv[1:]:
|
||||||
if file[-4:] == '.css':
|
data = read_file(file)
|
||||||
print cleancss(read_file(file))
|
open(file+'.bak2', 'w').write(data)
|
||||||
if file[-5:] == '.html':
|
if file[-4:] == '.css':
|
||||||
print cleanhtml(read_file(file))
|
data = cleancss(data)
|
||||||
|
if file[-5:] == '.html':
|
||||||
|
data = cleanhtml(data)
|
||||||
|
open(file, 'w').write(data)
|
||||||
|
|||||||
@@ -218,4 +218,3 @@ class LinuxService(ServiceBase):
|
|||||||
else:
|
else:
|
||||||
cmd = env['update-rc.d']
|
cmd = env['update-rc.d']
|
||||||
return [cmd, self.name, 'remove']
|
return [cmd, self.name, 'remove']
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ echo
|
|||||||
yum update
|
yum update
|
||||||
|
|
||||||
# Install required packages
|
# Install required packages
|
||||||
yum install httpd mod_ssl mod_wsgi wget python
|
yum install httpd mod_ssl mod_wsgi wget python unzip
|
||||||
|
|
||||||
###
|
###
|
||||||
### Phase 2 - Install web2py
|
### Phase 2 - Install web2py
|
||||||
|
|||||||
Reference in New Issue
Block a user