Commit 2bf32be8 authored by Aurélien Campéas's avatar Aurélien Campéas
Browse files

tsio: add a `delete` api call to entirely forget a series

Resolves #45.
parent b1155c32f092
......@@ -383,7 +383,7 @@ def test_revision_date(engine, tsh):
""", oldstate)
def test_deletion(engine, tsh):
def test_point_deletion(engine, tsh):
ts_begin = genserie(datetime(2010, 1, 1), 'D', 11)
ts_begin.iloc[-1] = np.nan
tsh.insert(engine, ts_begin, 'ts_del', 'test')
......@@ -865,6 +865,52 @@ def test_precision(engine, tsh):
assert diff is None
def test_serie_deletion(engine, tsh):
ts = genserie(datetime(2018, 1, 10), 'H', 10)
tsh.insert(engine, ts, 'keepme', 'Babar')
tsh.insert(engine, ts, 'deleteme', 'Celeste')
ts = genserie(datetime(2018, 1, 12), 'H', 10)
tsh.insert(engine, ts, 'keepme', 'Babar')
tsh.insert(engine, ts, 'deleteme', 'Celeste')
seriecount = engine.execute(
'select count(*) from {}.registry'.format(tsh.namespace)
).scalar()
csetcount = engine.execute(
'select count(*) from {}.changeset'.format(tsh.namespace)
).scalar()
csetcount2 = engine.execute(
'select count(*) from {}.changeset_series'.format(tsh.namespace)
).scalar()
assert csetcount == csetcount2
with engine.connect() as cn:
tsh.delete(cn, 'deleteme')
assert not tsh.exists(engine, 'deleteme')
log = [entry['author']
for entry in tsh.log(engine, names=('keepme', 'deleteme'))]
assert log == ['Babar', 'Babar']
csetcount3 = engine.execute(
'select count(*) from {}.changeset'.format(tsh.namespace)
).scalar()
csetcount4 = engine.execute(
'select count(*) from {}.changeset_series'.format(tsh.namespace)
).scalar()
seriecount2 = engine.execute(
'select count (*) from {}.registry'.format(tsh.namespace)
).scalar()
assert csetcount - csetcount3 == 2
assert csetcount2 - csetcount4 == 2
assert seriecount - seriecount2 == 1
with pytest.raises(AssertionError) as werr:
tsh.delete(engine, 'keepme')
assert werr.value.args[0] == 'use a transaction object'
def test_strip(engine, tsh):
for i in range(1, 5):
pubdate = utcdt(2017, 1, i)
......@@ -1120,7 +1166,7 @@ def test_rename(engine, tsh):
'bar': 'new-bar'
})
tsh.resetcaches()
tsh._resetcaches()
assert tsh.get(engine, 'foo') is None
assert tsh.get(engine, 'bar') is None
......
......@@ -2,7 +2,7 @@ import logging
from threading import Lock
from sqlalchemy import (Table, Column, Integer, String, MetaData, TIMESTAMP,
ForeignKey, PrimaryKeyConstraint)
ForeignKey, UniqueConstraint)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.schema import CreateSchema
......@@ -68,14 +68,14 @@ class tsschema(object):
changeset_series = Table(
'changeset_series', meta,
Column('cset', Integer,
ForeignKey('{}.changeset.id'.format(self.namespace)),
index=True, nullable=False),
ForeignKey('{}.changeset.id'.format(self.namespace), ondelete='set null'),
index=True, nullable=True),
Column('serie', Integer,
ForeignKey('{}.registry.id'.format(self.namespace)),
ForeignKey('{}.registry.id'.format(self.namespace), ondelete='cascade'),
index=True, nullable=False),
PrimaryKeyConstraint(
UniqueConstraint(
'cset', 'serie',
name='{}_changeset_series_pk'.format(self.namespace)),
name='{}_changeset_series_unique'.format(self.namespace)),
schema=self.namespace
)
......
......@@ -6,6 +6,7 @@ import hashlib
import pandas as pd
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.engine.base import Engine
from sqlalchemy.sql.expression import select, func, desc
from sqlalchemy.dialects.postgresql import BYTEA
......@@ -17,7 +18,7 @@ from tshistory.util import (
SeriesServices,
tzaware_serie
)
from tshistory.snapshot import Snapshot
from tshistory.snapshot import Snapshot, TABLES as SNAPTABLES
L = logging.getLogger('tshistory.tsio')
TABLES = {}
......@@ -279,6 +280,40 @@ class TimeSerie(SeriesServices):
sql = sql.where(cset.c.insertion_date >= revdate)
return cn.execute(sql).scalar()
def delete(self, cn, seriename):
assert not isinstance(cn, Engine), 'use a transaction object'
assert self.exists(cn, seriename)
# changeset will keep ghost entries
# we cleanup changeset series, then registry
# then we drop the two remaining tables
# cn *must* be a transaction scope
rid, tablename = cn.execute(
'select id, table_name from "{}".registry '
'where seriename = %(seriename)s'.format(self.namespace),
seriename=seriename
).fetchone()
# drop series tables
cn.execute(
'drop table "{}.timeserie"."{}" cascade'.format(self.namespace, tablename)
)
cn.execute(
'drop table "{}.snapshot"."{}" cascade'.format(self.namespace, tablename)
)
# cleanup changesets table
cn.execute('with csets as ('
' select cset from "{ns}".changeset_series '
' where serie = %(rid)s'
') '
'delete from "{ns}".changeset as cset using csets '
'where cset.id = csets.cset'.format(ns=self.namespace),
rid=rid
)
cn.execute('delete from "{}".registry '
'where id = %(rid)s'.format(self.namespace),
rid=rid)
# -> this will transitively cleanup state changeset_series entries
self._resetcaches()
def strip(self, cn, seriename, csid):
logs = self.log(cn, fromrev=csid, names=(seriename,))
assert logs
......@@ -546,9 +581,9 @@ class TimeSerie(SeriesServices):
)
cn.execute(sql)
# don't use this
def resetcaches(self):
def _resetcaches(self):
TABLES.clear()
SNAPTABLES.clear()
self.metadatacache.clear()
self.registry_map.clear()
self.serie_tablename.clear()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment