Skip to content

Commit

Permalink
adds explicit apply_revisions
Browse files Browse the repository at this point in the history
  • Loading branch information
David Erb committed May 7, 2023
1 parent 2030acf commit 6587115
Showing 1 changed file with 69 additions and 45 deletions.
114 changes: 69 additions & 45 deletions src/dls_normsql/aiosqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
logger = logging.getLogger(__name__)

connect_lock = asyncio.Lock()
apply_revisions_lock = asyncio.Lock()


# ----------------------------------------------------------------------------------------
Expand Down Expand Up @@ -60,7 +61,10 @@ def __init__(self, specification):

self.__tables = {}

self.LATEST_REVISION = 1
# Deriving class has not established its latest revision?
if not hasattr(self, "LATEST_REVISION") or self.LATEST_REVISION is None:
# Presume it is 1.
self.LATEST_REVISION = 1

self.__backup_restore_lock = asyncio.Lock()

Expand Down Expand Up @@ -100,8 +104,8 @@ async def connect(self):
# rows = await self.query("SELECT * from mainTable", why="main table check")

await self.__connection.create_function("regexp", 2, sqlite_regexp_callback)
logger.debug("created regexp function")

# Let the base class contribute its table definitions to the in-memory list.
await self.add_table_definitions()

if should_create_schemas:
Expand All @@ -111,42 +115,59 @@ async def connect(self):
)
# TODO: Set permission on sqlite file from configuration.
os.chmod(self.__filename, 0o666)
else:
try:
records = await self.query(
f"SELECT number FROM {Tablenames.REVISION}",
why="get database revision",
)
if len(records) == 0:
old_revision = 0
else:
old_revision = records[0]["number"]
except Exception as exception:
logger.warning(
f"could not get revision, presuming legacy database with no table: {exception}"
)
old_revision = 0

if old_revision < self.LATEST_REVISION:
logger.debug(
f"need to update old revision {old_revision}"
f" to latest revision {self.LATEST_REVISION}"
)
for revision in range(old_revision, self.LATEST_REVISION):
logger.debug(f"updating to revision {revision+1}")
await self.apply_revision(revision + 1)
await self.update(
Tablenames.REVISION,
{"number": self.LATEST_REVISION},
"1 = 1",
why="update database revision",
)

# Emit the name of the database file for positive confirmation on console.
logger.info(
f"{callsign(self)} database file is {self.__filename} revision {self.LATEST_REVISION}"
)

# ----------------------------------------------------------------------------------------
async def apply_revisions(self):
"""
Apply revision updates to databse if needed.
"""

# TODO: Consider how to lock database while running applying_revisions.
# TODO: Establish transaction arouund apply_revisions with rollback if error.
async with apply_revisions_lock:
try:
records = await self.query(
f"SELECT number FROM {Tablenames.REVISION}",
why="get database revision",
)
if len(records) == 0:
old_revision = 0
else:
old_revision = records[0]["number"]
except Exception as exception:
logger.warning(
f"could not get revision, presuming legacy database with no table: {exception}"
)
old_revision = 0

if old_revision < self.LATEST_REVISION:
# Backup before applying revisions.
logger.debug(
f"[BKREVL] backing up before updating to revision {self.LATEST_REVISION}"
)

await self.backup()

for revision in range(old_revision, self.LATEST_REVISION):
logger.debug(f"updating to revision {revision+1}")
await self.apply_revision(revision + 1)
await self.update(
Tablenames.REVISION,
{"number": self.LATEST_REVISION},
"1 = 1",
why="update database revision",
)
else:
logger.debug(
f"[BKREVL] no need to update old revision {old_revision}"
f" which matches latest revision {self.LATEST_REVISION}"
)

# ----------------------------------------------------------------------------------------
async def apply_revision(self, revision):
logger.debug(f"updating to revision {revision}")
Expand Down Expand Up @@ -469,25 +490,26 @@ async def backup(self):
Back up database to timestamped location.
"""

# Prune all the restores which were orphaned.
directory = self.__backup_directory
async with self.__backup_restore_lock:
# Prune all the restores which were orphaned.
directory = self.__backup_directory
if directory is None:
raise RuntimeError("no backup directory supplied in confirmation")

basename, suffix = os.path.splitext(os.path.basename(self.__filename))
basename, suffix = os.path.splitext(os.path.basename(self.__filename))

filenames = glob.glob(f"{directory}/{basename}.*{suffix}")
filenames = glob.glob(f"{directory}/{basename}.*{suffix}")

filenames.sort(reverse=True)
filenames.sort(reverse=True)

logger.debug(f"[BACKPRU] {self.__last_restore} is last restore")
for restore in range(self.__last_restore):
logger.debug(
f"[BACKPRU] removing {restore}-th restore {filenames[restore]}"
)
os.remove(filenames[restore])
for restore in range(self.__last_restore):
logger.debug(
f"[BACKPRU] removing {restore}-th restore {filenames[restore]}"
)
os.remove(filenames[restore])

self.__last_restore = 0
self.__last_restore = 0

async with self.__backup_restore_lock:
timestamp = isodatetime_filename()
to_filename = f"{directory}/{basename}.{timestamp}{suffix}"

Expand All @@ -509,6 +531,8 @@ async def restore(self, nth):

async with self.__backup_restore_lock:
directory = self.__backup_directory
if directory is None:
raise RuntimeError("no backup directory supplied in confirmation")

basename, suffix = os.path.splitext(os.path.basename(self.__filename))

Expand Down

0 comments on commit 6587115

Please sign in to comment.