"""
Helpers for dealing with obscore-published tables and its view.
"""

#c Copyright 2008-2025, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


import contextlib

from gavo import base


def createObscoreView(connection):
	"""create the (potentially materialised) obscore view.

	This is mainly called from stream in obscore.rd (see the
	create-obscore-view stream there).  You can call it programatically
	if the obscore view is not materialised.  Don't do it if it is, because
	this is probably an expensive operation, and you'll need to create
	indexes and such manually if you call this function.
	"""
	try:
		from gavo import rsc
		ocTable = rsc.TableForDef(
			base.resolveCrossId("//obscore#_obscoresources"),
			connection=connection)
		parts = ["(%s)"%row["sqlFragment"]
			for row in ocTable.iterQuery(ocTable.tableDef, "")]
		if parts:
			ocTable.dropTable("ivoa.obscore")

			if base.getConfig("ivoa", "materialiseObscore"):
				creation = "CREATE TABLE "
			else:
				creation = "CREATE VIEW"

			connection.execute(
				f"{creation} ivoa.ObsCore AS (%s)"%(
				" UNION ALL ".join(parts)))
			rsc.TableForDef(
				base.resolveCrossId("//obscore#ObsCore"),
				connection=connection).updateMeta()
	except:
		base.ui.notifyError(
			"--------  Try   dachs imp //obscore recover to fix this -------")
		raise


@contextlib.contextmanager
def suspendFromObscore(rd, connection):
	"""a context manager to remove everything within rd from obscore in
	the controlled block.

	This is a good idea when doing larger operations in the RD, as a
	singled locked table (e.g., while clustering) in ivoa.obscore will lock the
	whole thing.

	This will commit the connection, once before entering the controlled
	block, and once after, if there actually are obscore-published tables
	in RD.

	For a materialised obscore clause, this is a no-op.
	"""
	if base.getConfig("ivoa", "materialiseObscore"):
		updateObscore = lambda _: None
	else:
		updateObscore = createObscoreView

	prevClauses = list(connection.queryToDicts(
		"SELECT tablename, sqlfragment, sourcerd FROM ivoa._obscoresources"
		" WHERE sourceRD=%(sourceRD)s",
		{"sourceRD": rd.sourceId}))

	if not prevClauses:
		yield
	
	else:
		connection.execute("DELETE FROM ivoa._obscoresources"
			" WHERE sourceRD=%(sourceRD)s",
			{"sourceRD": rd.sourceId})
		updateObscore(connection)
		connection.commit()

		try:
			yield
		except:
			connection.rollback()
			raise

		finally:
			with connection.cursor() as c:
				c.executemany("INSERT INTO ivoa._obscoresources"
					" (tablename, sqlfragment, sourcerd)"
					" VALUES (%(tablename)s, %(sqlfragment)s, %(sourcerd)s)",
					prevClauses)
			updateObscore(connection)
			connection.commit()


def restoreObscore(connection):
	"""sees if this system should have an obscore table and re-creates
	it if it's missing.
	"""
	q = base.UnmanagedQuerier(connection)
	if q.getTableType("ivoa._obscoresources") is not None:
		n = list(q.connection.query(
			"SELECT count(*) from ivoa._obscoresources"))[0][0]
		if n>1: # ivoa.emptyobscore doesn't count
				createObscoreView(connection)

