provide access to SQLite, with functions and variables for db-specific variants

This commit is contained in:
Helmut Merz 2024-03-06 22:45:59 +01:00
parent 83071842c8
commit 592e653561
7 changed files with 69 additions and 22 deletions

1
.gitignore vendored
View file

@ -10,3 +10,4 @@
*#*# *#*#
*.#* *.#*
__pycache__ __pycache__
var

View file

@ -4,19 +4,28 @@
import base64 import base64
from sqlalchemy import create_engine, MetaData, text from sqlalchemy import create_engine, MetaData, text
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy import Integer
from sqlalchemy.dialects.sqlite import JSON
import threading import threading
import zope.sqlalchemy
def getEngine(dbtype, dbname, user, pw, host='localhost', port=5432, **kw): # predefined db-specific definitions, usable for SQLite;
return create_engine('%s://%s:%s@%s:%s/%s' % ( # may be overriden by import of ``scopes.storage.db.<dbname>``
dbtype, user, pw, host, port, dbname), **kw)
def sessionFactory(engine): def sessionFactory(engine):
Session = scoped_session(sessionmaker(bind=engine, twophase=True)) return engine.connect
zope.sqlalchemy.register(Session)
return Session def getEngine(dbtype, dbname, user, pw, host='localhost', port=5432, **kw):
return create_engine('%s:///%s' % (dbtype, dbname), **kw)
def mark_changed(session):
pass
def commit(conn):
conn.commit()
IdType = Integer
JsonType = JSON
# put something like this in code before first creating a Storage object # put something like this in code before first creating a Storage object
#engine = getEngine('postgresql+psycopg', 'testdb', 'testuser', 'secret') #engine = getEngine('postgresql+psycopg', 'testdb', 'testuser', 'secret')
@ -57,7 +66,8 @@ class Storage(object):
def dropTable(self, tableName): def dropTable(self, tableName):
with self.engine.begin() as conn: with self.engine.begin() as conn:
conn.execute(text('drop table if exists %s.%s' % (self.schema, tableName))) prefix = self.schema and self.schema + '.' or ''
conn.execute(text('drop table if exists %s%s' % (prefix, tableName)))
def resetSequence(self, tableName, colName, v): def resetSequence(self, tableName, colName, v):
sq = ('alter sequence %s.%s_%s_seq restart %i' % sq = ('alter sequence %s.%s_%s_seq restart %i' %

View file

@ -0,0 +1 @@
"""scopes.storage.db"""

View file

@ -0,0 +1,23 @@
# scopes.storage.db.postgres
"""Database-related code specific for PostgreSQL."""
from sqlalchemy import create_engine
from sqlalchemy import BigInteger, JSONB
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import scoped_session, sessionmaker
import transaction
from zope.sqlalchemy import register, mark_changed
def sessionFactory(engine):
Session = scoped_session(sessionmaker(bind=engine, twophase=True))
register(Session)
return Session
def getEngine(dbtype, dbname, user, pw, host='localhost', port=5432, **kw):
return create_engine('%s://%s:%s@%s:%s/%s' % (
dbtype, user, pw, host, port, dbname), **kw)
def commit(conn):
transaction.commit()

View file

@ -9,12 +9,10 @@ data (payload) represented as a dict.
import base64 import base64
from datetime import datetime from datetime import datetime
from sqlalchemy import Table, Column, Index from sqlalchemy import Table, Column, Index
from sqlalchemy import BigInteger, DateTime, Text, func from sqlalchemy import DateTime, Text, func
from sqlalchemy import and_ from sqlalchemy import and_
from sqlalchemy.dialects.postgresql import JSONB
import transaction
from zope.sqlalchemy import register, mark_changed
from scopes.storage.common import commit, IdType, JsonType, mark_changed
from scopes.storage.common import registerContainerClass from scopes.storage.common import registerContainerClass
@ -177,7 +175,7 @@ class Container(object):
def createTable(storage, tableName, headcols, indexes=None): def createTable(storage, tableName, headcols, indexes=None):
metadata = storage.metadata metadata = storage.metadata
cols = [Column('trackid', BigInteger, primary_key=True)] cols = [Column('trackid', IdType, primary_key=True)]
idxs = [] idxs = []
for ix, f in enumerate(headcols): for ix, f in enumerate(headcols):
cols.append(Column(f.lower(), Text, nullable=False, server_default='')) cols.append(Column(f.lower(), Text, nullable=False, server_default=''))
@ -187,7 +185,7 @@ def createTable(storage, tableName, headcols, indexes=None):
indexName = 'idx_%s_%d' % (tableName, (ix + 1)) indexName = 'idx_%s_%d' % (tableName, (ix + 1))
idxs.append(Index(indexName, *idef)) idxs.append(Index(indexName, *idef))
idxs.append(Index('idx_%s_ts' % tableName, 'timestamp')) idxs.append(Index('idx_%s_ts' % tableName, 'timestamp'))
cols.append(Column('data', JSONB, nullable=False, server_default='{}')) cols.append(Column('data', JsonType, nullable=False, server_default='{}'))
table = Table(tableName, metadata, *(cols+idxs), extend_existing=True) table = Table(tableName, metadata, *(cols+idxs), extend_existing=True)
metadata.create_all(storage.engine) metadata.create_all(storage.engine)
return table return table

View file

@ -7,8 +7,16 @@ server_port = '8999'
app = zope_app app = zope_app
# storage settings # storage settings
# PostgreSQL
dbengine = 'postgresql+psycopg' dbengine = 'postgresql+psycopg'
dbname = 'testdb' dbname = 'testdb'
dbuser = 'testuser' dbuser = 'testuser'
dbpassword = 'secret' dbpassword = 'secret'
dbschema = 'testing'
# SQLite
dbengine = 'sqlite'
dbname = 'var/test.db'
dbschema = None

View file

@ -2,27 +2,30 @@
"""Tests for the 'scopes.storage' package.""" """Tests for the 'scopes.storage' package."""
import config
from datetime import datetime from datetime import datetime
import transaction import transaction
import unittest import unittest
import scopes.storage.common import scopes.storage.common
from scopes.storage.common import Storage, getEngine, sessionFactory from scopes.storage.common import commit, Storage, getEngine, sessionFactory
from scopes.storage import proxy from scopes.storage import proxy
from scopes.storage import folder, tracking from scopes.storage import folder, tracking
import config
engine = getEngine(config.dbengine, config.dbname, config.dbuser, config.dbpassword) engine = getEngine(config.dbengine, config.dbname, config.dbuser, config.dbpassword)
scopes.storage.common.engine = engine scopes.storage.common.engine = engine
scopes.storage.common.Session = sessionFactory(engine) scopes.storage.common.Session = sessionFactory(engine)
storage = Storage(schema='testing') #storage = Storage(schema='testing')
#storage = Storage(schema=config.dbschema)
storage = Storage()
class Test(unittest.TestCase): class Test(unittest.TestCase):
"Basic tests for the cco.storage package." "Basic tests for the cco.storage package."
def testTracking(self): def test_001_tracking(self):
storage.dropTable('tracks') storage.dropTable('tracks')
tracks = storage.create(tracking.Container) tracks = storage.create(tracking.Container)
@ -68,9 +71,9 @@ class Test(unittest.TestCase):
self.assertEqual(n, 1) self.assertEqual(n, 1)
self.assertEqual(tracks.get(31), None) self.assertEqual(tracks.get(31), None)
transaction.commit() commit(storage.session)
def testFolder(self): def test_002_folder(self):
storage.dropTable('folders') storage.dropTable('folders')
root = folder.Root(storage) root = folder.Root(storage)
self.assertEqual(list(root.keys()), []) self.assertEqual(list(root.keys()), [])
@ -83,6 +86,9 @@ class Test(unittest.TestCase):
self.assertEqual(ch1.parent, top.rid) self.assertEqual(ch1.parent, top.rid)
assert list(top.keys()) == ['child1'] assert list(top.keys()) == ['child1']
#transaction.commit()
storage.session.commit()
def suite(): def suite():
return unittest.TestSuite(( return unittest.TestSuite((
unittest.TestLoader().loadTestsFromTestCase(Test), unittest.TestLoader().loadTestsFromTestCase(Test),