"""
Tests dealing with script parsing and execution.
"""

#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.


from gavo.helpers import testhelpers

from gavo import base
from gavo import rscdesc
from gavo import rsc
from gavo import rscdef
from gavo.rscdef import scripting

import tresc

class FakeRunner(scripting.ScriptRunner):
	"""a script runner that just notices something has run.

	Fetch the stuff run destructively by using getRunScripts()
	"""
	_scriptsRun = []

	@classmethod
	def getScriptsRun(cls):
		try:
			return cls._scriptsRun[:]
		finally:
			cls._scriptsRun[:] = []

	@classmethod
	def reset(cls):
		cls._scriptsRun = []

	def _prepare(self, script):
		self.toNote = script.id
	
	def run(self, table, **kwargs):
		self._scriptsRun.append(table.tableDef.id+"-"+self.toNote)

scripting.RUNNER_CLASSES["Fake"] = FakeRunner
scripting.Script._lang.validValues.add("Fake")


class ScriptTest(testhelpers.VerboseTest):
	"""tests for script elements
	
	(but not actual scripting, that's somewhere else)
	"""
	def testOkScripts(self):
		s = base.parseFromString(scripting.Script, '<script type="beforeDrop"'
			' lang="SQL" name="test">drop table foo</script>')
		self.assertEqual(s.type, "beforeDrop")
		self.assertEqual(s.name, "test")
		self.assertEqual(s.content_, "drop table foo")

	def testBadScripts(self):
		self.assertRaises(base.StructureError, base.parseFromString,
			*(scripting.Script, '<script type="bad">xy</script>'))
		self.assertRaises(base.StructureError, base.parseFromString,
			*(scripting.Script, '<script name="bad">xy</script>'))

	def testBadScriptType(self):
		self.assertRaisesWithMsg(base.StructureError,
			'At IO:\'<table id="foo" onDisk="True"><script type="newSource...\', (1, 56):'
			' Invalid script type newSource for table elements',
			base.parseFromString,
			(rscdef.TableDef,
				'<table id="foo" onDisk="True"><script type="newSource"/></table>'))

	def testOnDiskOnly(self):
		self.assertRaisesWithMsg(base.StructureError,
			'At IO:\'<table id="foo"><script lang="Fake" type="preCreation...\', (1, 56):'
			' Scripts are only supported for onDisk tables',
			base.parseFromString,
			(rscdef.TableDef,
				'<table id="foo"><script lang="Fake" type="preCreation"/></table>'))


class SQLSplittingTest(testhelpers.VerboseTest, metaclass=testhelpers.SamplesBasedAutoTest):
	grammar = scripting.getSQLScriptGrammar()

	def _runTest(self, sample):
		script, expectedNumberOfParts = sample
		parts = self.grammar.parseString(script)
		self.assertEqual(len(parts), expectedNumberOfParts,
			"Unexpected number of parts: %s"%len(parts))
	
	samples = [
		("select * from foo", 1),
		("select * from foo;", 1),
		("select * from foo where \"cra;zy\"=';'", 1),
		("select * from foo;update where bla=7", 2),
		("SELECT a=$$a;bc$$", 1),
		("SELECT a=$$abc$$;c", 2),
		("/* this should be ignored: ; */; this should be there", 1),
		("$funcdef$ stmt; stmt; $$deep;er$$ $funcdef$; two", 2),
		("$funcdef$ stmt\n\n; stmt; $$deep;er$$ $funcdef$; two", 2),
		("'multiline\nstring;'", 1),
		("statement1;\n -- Sp'\nstatement 2 'quoted';", 2),
	]


class ScriptingRunTest(testhelpers.VerboseTest):
	resources = [("conn", tresc.dbConnection)]

	def testFromTable(self):
		FakeRunner.reset()
		self.conn.execute("drop table if exists test.foo")
		res = base.parseFromString(rscdesc.RD,
			"""<resource schema="test"><table id="foo" onDisk="True">
				<script type="preCreation" lang="Fake" id="precre"/>
				<script type="preIndex" lang="Fake" id="prein"/>
				<script type="postCreation" lang="Fake" id="pocre"/>
				<script type="beforeDrop" lang="Fake" id="befdro"/>
				</table>
				<data id="imp">
					<make table="foo"/>
				</data>
				</resource>""")
		try:
			d = rsc.makeData(res.getById("imp"), connection=self.conn)
			self.assertEqual(FakeRunner.getScriptsRun(),
				["foo-precre", "foo-prein", "foo-pocre"])
		finally:
			d.dropTables(rsc.parseValidating)
			self.assertEqual(FakeRunner.getScriptsRun(), ["foo-befdro"])

	def testFromTableAndMake(self):
		res = base.parseFromString(rscdesc.RD,
			"""<resource schema="test"><table id="foo" onDisk="True">
				<script type="preCreation" lang="Fake" id="precre"/>
				<script type="preIndex" lang="Fake" id="prein"/>
				<script type="postCreation" lang="Fake" id="pocre"/>
				<script type="beforeDrop" lang="Fake" id="befdro"/>
				</table>
				<data id="imp">
					<make table="foo">
						<script type="preCreation" lang="Fake" id="mprecre"/>
						<script type="preIndex" lang="Fake" id="mprein"/>
						<script type="postCreation" lang="Fake" id="mpocre"/>
						<script type="beforeDrop" lang="Fake" id="mbefdro"/>
						<script type="preImport" lang="Fake" id="preimp"/>
						<script type="newSource" lang="Fake" id="newso"/>
					</make>
				</data>
				</resource>""")
		try:
			d = rsc.makeData(res.getById("imp"), connection=self.conn
				)
			self.assertEqual(FakeRunner.getScriptsRun(),
				[ 'foo-precre', 'foo-mprecre', 'foo-prein',
					'foo-mprein', 'foo-pocre', 'foo-mpocre'])
		finally:
			d.dropTables(rsc.parseValidating)
			self.assertEqual(FakeRunner.getScriptsRun(),
				["foo-befdro", "foo-mbefdro"])

	def testUpdatingScripts(self):
		res = base.parseFromString(rscdesc.RD,
			"""<resource schema="test"><table id="foo" onDisk="True">
				<script type="preCreation" lang="Fake" id="precre"/>
				<script type="postCreation" lang="Fake" id="pocre"/>
				</table>
				<data id="imp" updating="True">
					<make table="foo">
						<script type="beforeDrop" lang="Fake" id="mbefdro"/>
						<script type="preImport" lang="Fake" id="preimp"/>
					</make>
				</data>
				</resource>""")
		try:
			d = rsc.makeData(res.getById("imp"), connection=self.conn)
			# first run: things run all normal.
			self.assertEqual(FakeRunner.getScriptsRun(),
				['foo-precre', 'foo-preimp', 'foo-pocre'])

			# second run: no table scripts run because we're updating
			d = rsc.makeData(res.getById("imp"), connection=self.conn)
			self.assertEqual(FakeRunner.getScriptsRun(), [])

		finally:
			# drop scripts always run
			d.dropTables(rsc.parseValidating)
			self.assertEqual(FakeRunner.getScriptsRun(),
				["foo-mbefdro"])

	def testFailingPythonScript(self):
		rd = base.parseFromString(rscdesc.RD,
			"""<resource schema="test"><table id="foo" onDisk="True">
				<script type="preCreation" lang="python">
					raise ValueError("Broken Script")
				</script>
				</table>
				</resource>""")

		try:
			t = rsc.TableForDef(rd.getById("foo"), connection=self.conn)
			self.assertRaisesWithMsg(
				base.StructureError,
				'At IO:\'<resource schema="test"><table id="foo" onDisk="True"...\', line 2:'
				' Execution of python script anonymous failed: Broken Script',
				t.create,
				())
		finally:
			self.conn.rollback()

	def testFailingSQLScript(self):
		rd = base.parseFromString(rscdesc.RD,
			"""<resource schema="test"><table id="foo" onDisk="True">
				<script type="preCreation" lang="SQL">SELECT UPDATE CREATE</script>
				</table>
				</resource>""")
		try:
			t = rsc.TableForDef(rd.getById("foo"), connection=self.conn)
			self.assertRaisesWithMsg(
				base.StructureError,
				'At IO:\'<resource schema="test"><table id="foo" onDisk="True"...\', line 2:'
				' Execution of SQL script anonymous failed: syntax error at or near "CREATE"\nLINE 1: SELECT UPDATE CREATE\n                      ^\n',
				t.create,
				())
		finally:
			self.conn.rollback()


if __name__=="__main__":
	testhelpers.main(SQLSplittingTest)
