<resource schema="ivoa" resdir="__system">
	<meta name="creationDate">2011-03-25T10:23:00</meta>
	<meta name="subject">observational-astronomy</meta>
	<meta name="description">Definition and support code for the ObsCore
		data model and table.</meta>

	<STREAM id="obscore-columns">
		<doc>
			The columns of a (standard) obscore table.  This can be used
			to define a "native" obscore table (as opposed to the more usual
			mixins below that expose standard products via obscore.

			Even if you are sure you want to do this, better ask again...
		</doc>

		<index columns="s_ra,s_dec" kind="q3c" metaOnly="True"/>
		<index columns="obs_publisher_did" metaOnly="True"/>

		<stc>
			Polygon ICRS [s_region]
		</stc>

		<column name="dataproduct_type" type="text"
			utype="obscore:obsdataset.dataproducttype" ucd="meta.code.class"
			description="High level scientific classification of the data product,
				taken from an enumeration"
			verbLevel="5">
			<values>
				<option>image</option>
				<option>cube</option>
				<option>spectrum</option>
				<option>sed</option>
				<option>timeseries</option>
				<option>visibility</option>
				<option>event</option>
			</values>
			<property name="std">1</property>
		</column>

		<column name="dataproduct_subtype" type="text"
			utype="obscore:obsdataset.dataproductsubtype" ucd="meta.code.class"
			description="Data product specific type"
			verbLevel="15"/>
	
		<column name="calib_level" type="smallint" required="True"
			utype="obscore:obsdataset.caliblevel" ucd="meta.code;obs.calib"
			description="Amount of data processing that has been
				applied to the data"
			verbLevel="10" note="calib">
			<property name="std">1</property>
		</column>

		<meta name="note" tag="calib">
			The calib_level flag takes the following values:

			=== ===========================================================
			 0  Raw Instrumental data requiring instrument-specific tools
			 1  Instrumental data processable with standard tools
			 2  Calibrated, science-ready data without instrument signature
			 3  Enhanced data products (e.g., mosaics)
			=== ===========================================================
		</meta>

		<column name="obs_collection" type="text"
				utype="obscore:dataid.collection" ucd="meta.id"
				description="Name of a data collection (e.g., project name) this
					data belongs to"
				verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="obs_id" type="text" 
			utype="obscore:DataID.observationID" ucd="meta.id"
			description="Unique identifier for an observation"
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="obs_title" type="text"
			utype="obscore:dataid.title" ucd="meta.title;obs"
			description="Free-from title of the data set"
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="obs_publisher_did" type="text"
			utype="obscore:curation.publisherdid" ucd="meta.ref.ivoid"
			description="Dataset identifier assigned by the publisher."
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="obs_creator_did" type="text"
			utype="obscore:dataid.creatordid" ucd="meta.id"
			description="Dataset identifier assigned by the creator."
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="access_url" type="text"
			utype="obscore:access.reference" ucd="meta.ref.url"
			description="The URL at which to obtain the data set."
			verbLevel="1" displayHint="type=product">
			<property name="std">1</property>
		</column>

		<column name="access_format" type="text"
			description="MIME type of the resource at access_url"
			utype="obscore:access.format" ucd="meta.code.mime"
			verbLevel="5">
			<property name="std">1</property>
		</column>

		<column name="access_estsize" type="bigint"
			description="Estimated size of data product"
			unit="kbyte" utype="obscore:access.size" ucd="phys.size;meta.file"
			verbLevel="5">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="target_name" type="text" 
			description="Object a targeted observation targeted"
			utype="obscore:Target.Name" ucd="meta.id;src"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="target_class" type="text" 
			description="Class of the target object (star, QSO, ...)"
			utype="obscore:target.class" ucd="src.class"
			verbLevel="20">
			<property name="std">1</property>
		</column>

		<column name="s_ra" type="double precision"
			description="RA of (center of) observation, ICRS"
			unit="deg"  ucd="pos.eq.ra"
			utype="obscore:char.spatialaxis.coverage.location.coord.position2d.value2.c1"
			verbLevel="1">
			<property name="std">1</property>
		</column>
																					 
		<column name="s_dec" type="double precision"
			description="Dec of (center of) observation, ICRS"
			unit="deg" ucd="pos.eq.dec"
			utype="obscore:char.spatialaxis.coverage.location.coord.position2d.value2.c2"
			verbLevel="1">
			<property name="std">1</property>
		</column>

		<column name="s_fov" type="double precision"
			description="Approximate spatial extent for the region covered by the
				observation"
			unit="deg" ucd="phys.angSize;instr.fov"
			utype="obscore:char.spatialaxis.coverage.bounds.extent.diameter"
			verbLevel="5">
			<property name="std">1</property>
		</column>
			
		<column name="s_region" type="spoly" xtype="adql:REGION"
			description="Region covered by the observation, as a polygon"
			utype="obscore:char.spatialaxis.coverage.support.area"
			ucd="pos.outline;obs.field"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="s_resolution" type="double precision"
			description="Best spatial resolution within the data set"
			unit="arcsec"  
			utype="obscore:Char.SpatialAxis.Resolution.refval.value"
			ucd="pos.angResolution"
			verbLevel="15">
			<property name="std">1</property>
		</column>
		
		<column name="t_min" type="double precision"
			description="Lower bound of times represented in the data set, as MJD"
			unit="d" xtype="mjd"
			utype="obscore:char.timeaxis.coverage.bounds.limits.starttime"
			ucd="time.start;obs.exposure"
			verbLevel="10"
			displayHint="type=humanDate">
			<property name="std">1</property>
		</column>
		
		<column name="t_max" type="double precision"
			description="Upper bound of times represented in the data set, as MJD"
			unit="d" xtype="mjd"
			utype="obscore:char.timeaxis.coverage.bounds.limits.stoptime"
			ucd="time.end;obs.exposure"
			verbLevel="10"
			displayHint="type=humanDate">
			<property name="std">1</property>
		</column>

		<column name="t_exptime" type="real"
			description="Total exposure time"
			unit="s" utype="obscore:char.timeaxis.coverage.support.extent"
			ucd="time.duration;obs.exposure"
			verbLevel="10">
			<property name="std">1</property>
		</column>
	 
		<column name="t_resolution" type="real" 
			description="Minimal significant time interval along the time axis"
			unit="s" utype="obscore:char.timeaxis.resolution.refval.value" ucd="time.resolution"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="em_min" type="double precision"
			description="Minimal wavelength represented within the data set"
			unit="m" utype="obscore:char.spectralaxis.coverage.bounds.limits.lolimit"
			ucd="em.wl;stat.min"
			verbLevel="10">
			<property name="std">1</property>
		</column>
																			
		<column name="em_max" type="double precision"
			description="Maximal wavelength represented within the data set"
			unit="m" utype="obscore:char.spectralaxis.coverage.bounds.limits.hilimit"
			ucd="em.wl;stat.max"
			verbLevel="10">
			<property name="std">1</property>
		</column>

		<column name="em_res_power" type="double precision"
			description="Spectral resolving power lambda/delta lamda"
			utype="obscore:char.spectralaxis.resolution.resolpower.refval"
			ucd="spect.resolution"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="o_ucd" type="text"
			description="UCD for the product's observable"
			utype="obscore:char.observableaxis.ucd" ucd="meta.ucd"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="pol_states" type="text"
			description="List of polarization states in the data set"
			utype="obscore:Char.PolarizationAxis.stateList"
			ucd="meta.code;phys.polarization"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="facility_name" type="text"
			description="Name of the facility at which data was taken"
			utype="obscore:Provenance.ObsConfig.facility.name"
			ucd="meta.id;instr.tel"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="instrument_name" type="text"
			description="Name of the instrument that produced the data"
			utype="obscore:Provenance.ObsConfig.instrument.name"
			ucd="meta.id;instr"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<!-- obscore 1.1 additions -->

		<column name="s_xel1" type="bigint"
			description="Number of elements (typically pixels) along the
				first spatial axis."
			utype="obscore:Char.SpatialAxis.numBins1"
			ucd="meta.number"
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="s_xel2" type="bigint"
			description="Number of elements (typically pixels) along the
				second spatial axis."
			utype="obscore:Char.SpatialAxis.numBins2"
			ucd="meta.number"
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="t_xel" type="bigint"
			description="Number of elements (typically pixels) along the
				time axis."
			utype="obscore:Char.TimeAxis.numBins"
			ucd="meta.number"
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="em_xel" type="bigint"
			description="Number of elements (typically pixels) along the
				spectral axis."
			utype="obscore:Char.SpectralAxis.numBins"
			ucd="meta.number"
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="pol_xel" type="bigint"
			description="Number of elements (typically pixels) along the
				polarization axis."
			utype="obscore:Char.PolarizationAxis.numBins"
			ucd="meta.number"
			verbLevel="10">
			<property name="std">1</property>
			<values nullLiteral="-1"/>
		</column>

		<column name="s_pixel_scale" type="double precision"
			description="Sampling	period in world	coordinate	units	along	
				the	spatial	axis"
			unit="arcsec"
			utype="obscore:Char.SpatialAxis.Sampling.RefVal.SamplingPeriod"
			ucd="phys.angSize;instr.pixel"
			verbLevel="10">
			<property name="std">1</property>
		</column>

		<column name="em_ucd" type="text"
			description="Nature of the product's spectral axis (typically, em.freq,
				em.wl, or em.energy)"
			utype="obscore:Char.SpectralAxis.ucd" ucd="meta.ucd"
			verbLevel="15">
			<property name="std">1</property>
		</column>

		<column name="preview" type="text"
			ucd="meta.ref.url;meta.preview"
			description="URL of a preview (low-resolution, quick-to-retrieve 
				representation) of the data."
			verbLevel="15"
			displayHint="type=product"/>

		<column name="source_table" type="text"
			ucd="meta.id;meta.table"
			description="Name of a TAP-queriable table this data originates from.
				This source table usually provides more information on the
				the data than what is given in obscore.  See the TAP_SCHEMA of
				the originating TAP server for details."
			verbLevel="25"/>


		<FEED source="%#obscore-extracolumns"/>
	</STREAM>


	<table id="ObsCore" adql="True" onDisk="True" system="True">
		<property key="supportsModel">Obscore-1.1</property>
		<property key="supportsModelURI"
			>ivo://ivoa.net/std/ObsCore#core-1.1</property>

		<meta name="title">\getConfig{web}{sitename} Obscore Table</meta>

		<meta name="_associatedDatalinkService">
			<meta name="serviceId">//obscore#dl</meta>
			<meta name="idColumn">obs_publisher_did</meta>
		</meta>

		<meta name="description">The IVOA-defined obscore table, containing
		generic metadata for datasets within this datacenter.</meta>

		<!-- the view creation statement is bogus (a fallback used
		in the initial creation, actually; in reality, we create
		the view creation statement from the _obscoresources table in the
		data create -->

		<viewStatement>
			create view \qName (\colNames) as (select * from (VALUES(
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL, NULL, NULL, 
				NULL, NULL, NULL, NULL, NULL,
				NULL, NULL, NULL)) as q WHERE 0=1);
		</viewStatement>

		<FEED source="obscore-columns"/>
	</table>
	
	<table id="_obscoresources" onDisk="True"
			dupePolicy="overwrite" primary="tableName" system="True">
		<meta name="description">
			This table contains the SQL fragments that make up this installation's
			ivoa.obscore view.  Whenever a participating table is re-made, the
			view definition is renewed with a statement made up of a union of
			all sqlFragments present in this table.

			Manipulate this table through gavo imp on tables that have an obscore
			mixin, or by dropping RDs or purging tables that are part of obscore.
		</meta>
		<column name="tableName" type="text"
			description="Name of the table that generated this fragment (ususally
				through a mixin)"/>
		<column name="sqlFragment" type="text"
			description="The SQL fragment contributed from tableName"/>
		<column name="sourcerd" type="text"
			description="The RD the table was found in at input (this is
				mainly to support dachs drop -f"/>
	</table>
	
	<STREAM id="maintenance-scripts">
		<doc>
			These scripts must be included in any table that eventually
			ends up as a part of ivoa.obscore.
		</doc>

		<script id="addTableToObscoreSources" name="addTableToObscore"
				lang="python" type="afterMeta">
			obscoreClause = table.tableDef.expand(
				table.tableDef.getProperty("obscoreClause"))
		
			for srcRE, replacement in [
				# The following is copied in the mixin's processLate
				(r"CAST\(__COMPUTE__ AS text\) AS obs_publisher_did",
					"('ivo://\getConfig{ivoa}{authority}/~?' ||"
								" gavo_urlescape(accref)) AS obs_publisher_did")]:
				obscoreClause = re.sub(srcRE, replacement, obscoreClause)

			from gavo import rsc
			ots = rsc.TableForDef(
				base.caches.getRD("//obscore").getById("_obscoresources"),
				connection=table.connection)
			ots.addRow({"tableName": table.tableDef.getQName(),
				"sourcerd": table.tableDef.rd.sourceId,
				"sqlFragment": "SELECT %s FROM %s"%(
					obscoreClause, table.tableDef.getQName())})
		</script>

		<script name="createObscoreView" type="afterMeta" lang="python"
				id="createObscoreView">
			try:
				from gavo import rsc
				ocTable = rsc.TableForDef(
					base.resolveCrossId("//obscore#_obscoresources"),
					connection=table.connection)
				parts = ["(%s)"%row["sqlFragment"]
					for row in ocTable.iterQuery(ocTable.tableDef, "")]
				if parts:
					table.connection.execute("drop view if exists ivoa.ObsCore")
					table.connection.execute("create view ivoa.ObsCore as (%s)"%(
						" UNION ALL ".join(parts)))
					rsc.TableForDef(
						base.resolveCrossId("//obscore#ObsCore"),
						connection=table.connection).updateMeta()
			except:
				base.ui.notifyError(
					"--------  Try   dachs imp //obscore recover to fix this -------")
				raise
		</script>

		<script id="removeTableFromObscoreSources" 
				lang="python" 
				type="beforeDrop"
				name="removeTableFromObscoreSources">
			from gavo import rsc
			if table.getTableType("ivoa._obscoresources"):
				table.connection.execute(
					"DELETE FROM ivoa._obscoresources WHERE tableName=%(name)s",
					{"name": table.tableDef.getQName()})
				# importing this table may take a long time, and we don't want
				# to have obscore offline for so long; so, we immediately recreate
				# it.
				rsc.makeData(base.caches.getRD("//obscore").getById("create"),
					connection=table.connection)
		</script>
	</STREAM>

	<STREAM id="_publishCommon">
		<doc>
			Common elements for (almost all) ObsTAP publication mixins.
		</doc>
		<mixinPar name="dataproduct_subtype" 
			alias="productSubtype" 
			description="File subtype.  Details pending"
			>NULL</mixinPar>
		<mixinPar name="calib_level"
			alias="calibLevel" 
			description="Calibration level of data, a number between 0 and 3; 
				for details, see 
				http://dc.g-vo.org/tableinfo/ivoa.obscore#note-calib"
			>0</mixinPar>
		<mixinPar name="obs_collection"
			alias="collectionName" 
			description="A human-readable name for this collection.  This should 
				be short, so don't just use the resource title"
				>'unnamed'</mixinPar>
		<mixinPar name="target_name"
			alias="targetName" 
			description="Name of the target object."
			>NULL</mixinPar>
		<mixinPar name="target_class"
			alias="targetClass" 
			description="Class of target object(s).  You should take 
				whatever you put here from 
				http://simbad.u-strasbg.fr/guide/chF.htx">NULL</mixinPar>
		<mixinPar name="t_resolution"
			alias="tResolution" 
			description="Temporal resolution (in seconds)"
			>NULL</mixinPar>
		<mixinPar name="em_res_power"
			alias="emResPower" 
			description="Spectral resolution as lambda/delta lambda"
			>NULL</mixinPar>
		<mixinPar name="t_exptime"
			alias="expTime" 
			description="Total time of event counting.  This simply is 
				t_max-t_min for simple exposures.">NULL</mixinPar>
		<mixinPar name="pol_states"
			alias="polStates" 
			description="List of polarization states present in the data; 
				if you give something, use the convention of choosing the appropriate 
				from  {I Q U V RR LL RL LR XX YY XY YX POLI POLA} and write them 
				*in alphabetical order* with / separators, e.g. /I/Q/XX/."
			>NULL</mixinPar>
		<mixinPar name="facility_name"
			alias="facilityName" 
			description="The institute or observatory at which the data was produced"
			>NULL</mixinPar>
		<mixinPar name="s_xel1"
			alias="sXel1" 
			description="Number of pixels along the first spatial axis"
			>NULL</mixinPar>
		<mixinPar name="s_xel2"
			alias="sXel2" 
			description="Number of pixels along the second spatial axis"
			>NULL</mixinPar>
		<mixinPar name="t_xel" 
			alias="tXel" 
			description="Number of samples along the time axis"
			>NULL</mixinPar>
		<mixinPar name="em_xel"
			alias="emXel" 
			description="Number of samples along the spectral axis"
			>NULL</mixinPar>
		<mixinPar name="pol_xel"
			alias="polXel" 
			description="Number of polarisation states in this product"
			>NULL</mixinPar>
		<mixinPar name="s_pixel_scale"
			alias="sPixelScale" 
			description="Size of a spatial pixel (in arcsec)"
			>NULL</mixinPar>
		<mixinPar name="em_ucd"
			alias="emUCD" 
			description="UCD of the spectral axis as defined by the spectrum DM, 
				plus a few values defined in obscore 1.1 for Doppler axes"
			>NULL</mixinPar>
		<mixinPar name="preview" 
			description="URL of a preview for the data.  Set to NULL if you 
				don't have any previews."
			>NULL</mixinPar>
		<mixinPar name="source_table"
			alias="sourceTable" 
			description="The table this is mixed into (you probably don't want
			to change this)."
			>'\qName'</mixinPar>

		<mixinPar name="createDIDIndex" description="Index whatever expression
			you give for the dataset identifier?  You probably want this
			on tables with more than a few hundred datasets unless the DID is
			in a column that you already index anyway.">False</mixinPar>

		<FEED source="%#obscore-extrapars"/>

		<events>
			<adql>True</adql>
			<!-- the casts in the following table are there to keep postgres
			from inferring weird types when parts of the union have NULL
			entries-->
			<property name="obscoreClause">
						CAST(\dataproduct_type AS text) AS dataproduct_type,
						CAST(\dataproduct_subtype AS text) AS dataproduct_subtype,
						CAST(\calib_level AS smallint) AS calib_level,
						CAST(\obs_collection AS text) AS obs_collection,
						CAST(\obs_id AS text) AS obs_id,
						CAST(\obs_title AS text) AS obs_title,
						CAST(\obs_publisher_did AS text) AS obs_publisher_did,
						CAST(\obs_creator_did AS text) AS obs_creator_did,
						CAST(\access_url AS text) AS access_url,
						CAST(\access_format AS text) AS access_format,
						CAST(\access_estsize AS bigint) AS access_estsize,
						CAST(\target_name AS text) AS target_name,
						CAST(\target_class AS text) AS target_class,
						CAST(\s_ra AS double precision) AS s_ra,
						CAST(\s_dec AS double precision) AS s_dec,
						CAST(\s_fov AS double precision) AS s_fov,
						CAST(\s_region AS spoly) AS s_region,
						CAST(\s_resolution AS double precision) AS s_resolution,
						CAST(\t_min AS double precision) AS t_min,
						CAST(\t_max AS double precision) AS t_max,
						CAST(\t_exptime AS double precision) AS t_exptime,
						CAST(\t_resolution AS double precision) AS t_resolution,
						CAST(\em_min AS double precision) AS em_min,
						CAST(\em_max AS double precision) AS em_max,
						CAST(\em_res_power AS double precision) AS em_res_power,
						CAST(\o_ucd AS text) AS o_ucd,
						CAST(\pol_states AS text) AS pol_states,
						CAST(\facility_name AS text) AS facility_name,
						CAST(\instrument_name AS text) AS instrument_name,
						CAST(\s_xel1 AS bigint) AS s_xel1,
						CAST(\s_xel2 AS bigint) AS s_xel2,
						CAST(\t_xel AS bigint) AS t_xel,
						CAST(\em_xel AS bigint) AS em_xel,
						CAST(\pol_xel AS bigint) AS pol_xel,
						CAST(\s_pixel_scale AS double precision) AS s_pixel_scale,
						CAST(\em_ucd AS text) AS em_ucd,
						CAST(\preview AS text) AS preview,
						CAST(\source_table AS text) AS source_table
			</property>
			<FEED source="%#obscore-extraevents"/>
			<FEED source="maintenance-scripts"/>
		</events>

		<processLate id="obscore-processLate">
			<doc>
				Find all data items importing the table and furnish them
				with the scripts necessary to update the obscore view.
			</doc>
			<!-- see //products#hackProductsData for why this is a huge pain in
			the neck and how to get out of this. -->
			<setup>
				<code>
					from gavo import rscdef
				</code>
			</setup>
			<code><![CDATA[
				if not substrate.onDisk:
					raise base.StructureError("Only onDisk tables can be obscore"
						" published, but %s is not."%substrate.id)

				if base.parseBooleanLiteral(mixinPars["createDIDIndex"]):
					content = mixinPars["obs_publisher_did"]
					if content=='__COMPUTE__':
						# The following is copied in addTableToObscoreSources
						content = ("('ivo://\getConfig{ivoa}{authority}/~?' ||"
							"gavo_urlescape(accref))")
					substrate.feedObject("index",
						base.makeStruct(rscdef.DBIndex, 
							content_=content,
							name="obscore_%s_did_index"%substrate.id))
			]]></code>
		</processLate>
	</STREAM>

	<STREAM id="_publishProduct">
		<doc>
			publish mixin parameters deriving from products#table interface
			fields.
		</doc>
		<mixinPar name="obs_id" 
			alias="obsId"
			description="Identifier of the data set.  Only change this when 
				you do not mix in products."
			>accref</mixinPar>
		<mixinPar name="obs_publisher_did" 
			alias="did"
			description="Global identifier of the data set.  Leave __COMPUTE__ 
				for tables mixing in products."
			>__COMPUTE__</mixinPar>
		<mixinPar name="access_url"
			alias="accessURL" 
			description="URL at which the product can be obtained.  Leave as is 
				for tables mixing in products."
			>accref</mixinPar>
		<mixinPar name="access_format"
			alias="mime" 
			description="The MIME type of the product file.  Only touch if 
				you do not mix in products."
			>mime</mixinPar>
		<mixinPar name="access_estsize"
			alias="size"
			description="The estimated size of the product in kilobytes.  
				Only touch when you do not mix in products#table."
			>accsize/1024</mixinPar>
		<mixinPar name="preview" description="The preview URL will
			point to the products service; if you don't have any previews,
			you will have to set this to NULL manually."
				>accref || '?preview=True'</mixinPar>
	</STREAM>

	<mixinDef id="publish">
		<doc>
			Publish this table to ObsTAP.

			This means mapping or giving quite a bit of data from the present
			table to ObsCore rows.  Internally, this information is converted
			to an SQL select statement used within a create view statement.
			In consequence, you must give *SQL* expressions in the parameter 
			values; just naked column names from your input table are ok,
			of course.  Most parameters are set to NULL or appropriate
			defaults for tables mixing in //products#table.

			Since the mixin generates script elements, it cannot be used
			in untrusted RDs.  The fact that you can enter raw SQL also
			means you will get ugly error messages if you give invalid
			parameters.

			Some items are filled from product interface fields automatically.
			You must change these if you obscore-publish tables not mixin
			in products.

			Note: you must say ``dachs imp //obscore`` before anything 
			obscore-related will work.
		</doc>

		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="dataproduct_type"
			alias="productType" 
			description="Data product type; one of image, cube, spectrum, sed, 
				timeseries, visibility, event, or NULL if None of the above"/>
		<mixinPar name="obs_title"
			alias="title" 
			description="A human-readable title of the data set."
			>NULL</mixinPar>
		<mixinPar name="s_ra"
			alias="ra" 
			description="Center RA"
			>NULL</mixinPar>
		<mixinPar name="s_dec"
			alias="dec" 
			description="Center Dec"
			>NULL</mixinPar>
		<mixinPar name="s_fov"
			alias="fov" 
			description="Approximate diameter of region covered"
			>NULL</mixinPar>
		<mixinPar name="s_region" 
			alias="coverage"
			description="A polygon giving the spatial coverage of the data 
				set; this must always be in ICRS.  This is cast to an pgsphere 
				spoly, which currently means that you have to provide an spoly 
				(reference), too."
			>NULL</mixinPar>
		<mixinPar name="t_min"
			alias="tMin" 
			description="MJD for the lower bound of times covered in the data 
				set (e.g. start of exposure).  Use ts_to_mjd(ts) to get this 
				from a postgres timestamp."
			>NULL</mixinPar>
		<mixinPar name="t_max"
			alias="tMax" 
			description="MJD for the upper bound of times covered in the data 
			set.  See tMin"
			>NULL</mixinPar>
		<mixinPar name="em_min"
			alias="emMin" 
			description="Lower bound of wavelengths represented in the 
			data set, in meters."
			>NULL</mixinPar>
		<mixinPar name="em_max"
			alias="emMax" 
			description="Upper bound of wavelengths represented in the data 
				set, in meters."
			>NULL</mixinPar>
		<mixinPar name="o_ucd"
			alias="oUCD" 
			description="UCD of the observable quantity, e.g., em.opt for 
				wide-band optical frames."
			>NULL</mixinPar>
		<mixinPar name="s_resolution"
			alias="sResolution" description="The (best) angular resolution 
				within the data set, in arcsecs"
			>NULL</mixinPar>
		<mixinPar name="instrument_name"
			alias="instrumentName" 
			description="The instrument that produced the data"
			>NULL</mixinPar>
		<mixinPar name="obs_creator_did"
			alias="creatorDID" 
			description="Global identifier of the data set assigned by the 
				creator.  Leave NULL unless the creator actually assigned an 
				IVO id themselves.">NULL</mixinPar>
	</mixinDef>

	<mixinDef id="publishSIAP">
		<doc>
			Publish a PGS SIAP table to ObsTAP.

			This works like //obscore#publish except some defaults apply
			that copy fields that work analoguously in SIAP and in ObsTAP.

			For special situations, you can, of course, override any
			of the parameters, but most of them should already be all right.
			To find out what the parameters described as "preset for SIAP"
			mean, refer to //obscore#publish.

			Note: you must say ``dachs imp //obscore`` before anything 
			obscore-related will work.
		</doc>
		
		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="dataproduct_type"
			alias="productType" 
			description="preset for SIAP"
			>'image'</mixinPar>
		<mixinPar name="obs_title"
			alias="title" 
			description="preset for SIAP"
			>imageTitle</mixinPar>
		<mixinPar name="s_ra" 
			alias="ra" 
			description="preset for SIAP"
			>centerAlpha</mixinPar>
		<mixinPar name="s_dec"
			alias="dec" 
			description="preset for SIAP"
			>centerDelta</mixinPar>
		<mixinPar name="s_fov"
			alias="fov" 
			description="preset for SIAP; we use the
			extent along the X axis as a very rough estimate for the size.  If 
			you can do better, by all means do."
			>pixelScale[1]*pixelSize[1]</mixinPar>
		<mixinPar name="s_region"
			alias="coverage" 
			description="preset for SIAP"
			>coverage</mixinPar>
		<mixinPar name="t_min" 
			alias="tMin" 
			description="preset for SIAP; if you want, change this to start 
				of observation as available."
			>dateObs</mixinPar>
		<mixinPar name="t_max"
			alias="tMax" 
			description="preset for SIAP; if you want, change this to end of 
				observation as available."
			>dateObs</mixinPar>
		<mixinPar name="em_min"
			alias="emMin" 
			description="preset for SIAP"
			>bandpassLo</mixinPar>
		<mixinPar name="em_max"
			alias="emMax" 
			description="preset for SIAP"
			>bandpassHi</mixinPar>
		<mixinPar name="o_ucd"
			alias="oUCD" 
			description="preset for SIAP; fix if you either know more about 
			the band or if your images are not in the optical."
			>'em.opt'</mixinPar>
		<mixinPar name="s_resolution"
			alias="sResolution" 
			description="preset for SIAP; this is just the pixel scale in 
				one dimension.  If that's seriously wrong or you have uncalibrated 
				images in your collection, you may need to be more careful here."
			>pixelScale[1]*3600</mixinPar>
		<mixinPar name="instrument_name"
			alias="instrumentName" 
			description="The instrument that produced the data"
			>instId</mixinPar>
		<mixinPar name="obs_creator_did"
			alias="creatorDID" 
			description="Global identifier of the data set assigned by 
				the creator.  Leave NULL unless the creator actually assigned 
				an IVO id themselves.">NULL</mixinPar>
		<mixinPar name="s_xel1"
			alias="sXel1" 
			description="preset for SIAP"
			>pixelSize[1]</mixinPar>
		<mixinPar name="s_xel2"
			alias="sXel2" 
			description="preset for SIAP"
			>pixelSize[2]</mixinPar>
		<mixinPar name="s_pixel_scale"
			alias="sPixelScale" 
			description="preset for SIAP"
			>pixelScale[1]*3600</mixinPar>
		<mixinPar name="facility_name"
			alias="facilityName"
			description="Default is to take from resource meta"
			>\sqlquote{\metaSeq{facility}{NULL}}</mixinPar>
	</mixinDef>

	<mixinDef id="publishSSAPHCD">
		<doc>
			Publish a table mixing in //ssap#hcd to ObsTAP.  Since //ssap#hcd
			is deprecated, this should not be used in new RDs, either.  For
			//ssap#mixc tables, use publishSSAPMIXC.

			This works like `the //obscore#publish mixin`_ except some defaults apply
			that copy fields that work analoguously in SSAP and in ObsTAP.

			The columns already set in SSAP are marked as UNDOCUMENTED in the
			parameter list below.  For special situations, you can, of course,
			override any of the parameters.  To find out what they actually mean,
			mean, refer to `the //obscore#publish mixin`_.

			Note that this mixin does *not* set coverage (obscore: s_region).
			This is because although we could make a circle from ssa_location
			and ssa_aperture, circles are not allowed in DaCHS' s_region (which
			has a fixed type of spoly).  The recommended solution to still
			have s_region is to add (and index) a custom field in the ssa table 
			and compute some sort of spolys for the coverage.

			Note: you must say ``dachs imp //obscore`` before anything 
			obscore-related will work.
		</doc>

		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="s_region" alias="coverage" 
			description="Use ssa_region when the
			table also mixes in //ssap#simpleCoverage"
			>NULL</mixinPar>
		<mixinPar name="obs_collection" alias="collectionName"
			>\sqlquote{\getParam{ssa_collection}{NULL}}</mixinPar>
		<mixinPar name="obs_creator_did" alias="creatorDID"
			>ssa_creatorDID</mixinPar>
		<mixinPar name="s_dec" alias="dec">degrees(lat(ssa_location))</mixinPar>
		<mixinPar name="s_ra" alias="ra">degrees(long(ssa_location))</mixinPar>
		<mixinPar name="em_max" alias="emMax">ssa_specend</mixinPar>
		<mixinPar name="em_min" alias="emMin">ssa_specstart</mixinPar>
		<mixinPar name="t_exp_time" alias="expTime">ssa_timeExt</mixinPar>
		<mixinPar name="s_fov" alias="fov">ssa_aperture</mixinPar>
		<mixinPar name="instrument_name" alias="instrumentName"
			>\sqlquote{\getParam{ssa_instrument}{NULL}}</mixinPar>
		<mixinPar name="o_ucd" alias="oUCD"
			>\sqlquote{\getParam{ssa_fluxucd}}</mixinPar>
		<mixinPar name="dataproduct_type" alias="productType">'spectrum'</mixinPar>
		<mixinPar name="s_resolution" alias="sResolution"
			>\getParam{ssa_spaceRes}{NULL}/3600.</mixinPar>
		<mixinPar name="t_max" alias="tMax"
			>ssa_dateObs+ssa_timeExt/43200.</mixinPar>
		<mixinPar name="t_min" alias="tMin"
			>ssa_dateObs-ssa_timeExt/43200.</mixinPar>
		<mixinPar name="target_name" alias="targetName">ssa_targname</mixinPar>
		<mixinPar name="target_class" alias="targetClass">ssa_targclass</mixinPar>
		<mixinPar name="obs_title" alias="title">ssa_dstitle</mixinPar>
		<mixinPar name="em_ucd" alias="emUCD"
			>\sqlquote{\getParam{ssa_spectralucd}}</mixinPar> 
		<mixinPar name="obs_publisher_did" alias="did">ssa_pubdid</mixinPar>
		<mixinPar name="em_xel">ssa_length</mixinPar>
	</mixinDef>

	<mixinDef id="publishSSAPMIXC">
		<doc>
			Publish a table mixing in //ssap#view (or the deprecated //ssap#mixc) 
			to ObsTAP.

			This works like `the //obscore#publish mixin`_ except some defaults apply
			that copy fields that work analoguously in SSAP and in ObsTAP.

			The columns already set in SSAP are marked as UNDOCUMENTED in the
			parameter list below.  For special situations, you can, of course,
			override any of the parameters.  To find out what they actually mean,
			mean, refer to `the //obscore#publish mixin`_.

			Note that this mixin does *not* set coverage (obscore: s_region).
			This is because although we could make a circle from ssa_location
			and ssa_aperture, circles are not allowed in DaCHS' s_region (which
			has a fixed type of spoly).  The recommended solution to still
			have s_region is to add (and index) a custom field; the
			//ssap#simpleCoverage will do this.

			Note: you must say ``dachs imp //obscore`` before anything 
			obscore-related will work.
		</doc>

		<LFEED source="_publishCommon"/>
		<LFEED source="_publishProduct"/>

		<mixinPar name="s_region" alias="coverage" 
			description="Use ssa_region when the
			table also mixes in //ssap#simpleCoverage"
			>NULL</mixinPar>
		<mixinPar name="obs_collection" alias="collectionName"
			description="Default should work for you"
			>ssa_collection</mixinPar>
		<mixinPar name="obs_creator_did" alias="creatorDID"
			description="Default should work for you"
			>ssa_creatorDID</mixinPar>
		<mixinPar name="s_dec" alias="dec"
			description="Default should work for you; for large data collections,
				consider having a separate Dec column with a q3c index."
			>degrees(lat(ssa_location))</mixinPar>
		<mixinPar name="s_ra" alias="ra"
			description="Default should work for you; for large data collections,
				consider having a separate RA column with a q3c index."
			>degrees(long(ssa_location))</mixinPar>
		<mixinPar name="em_max" alias="emMax"
			description="Default should work for you"
			>ssa_specend</mixinPar>
		<mixinPar name="em_min" alias="emMin"
			description="Default should work for you"
			>ssa_specstart</mixinPar>
		<mixinPar name="em_xel" alias="emXel"
			description="Default should work for you"
			>ssa_length</mixinPar>
		<mixinPar name="t_exp_time" alias="expTime"
			description="Default should work for you"
			>ssa_timeExt</mixinPar>
		<mixinPar name="s_fov" alias="fov"
			description="Default should work for you"
			>ssa_aperture</mixinPar>
		<mixinPar name="instrument_name" alias="instrumentName"
			description="Default should work for you"
			>ssa_instrument</mixinPar>
		<mixinPar name="facility_name" alias="facilityName"
			description="Default should work for you"
			>\sqlquote{\metaSeq{facility}{NULL}}</mixinPar>
		<mixinPar name="o_ucd" alias="oUCD"
			description="Default should work for you"
			>\sqlquote{\getParam{ssa_fluxucd}}</mixinPar>
		<mixinPar name="dataproduct_type" alias="productType"
			description="Default should work for you"
			>ssa_dstype</mixinPar>
		<mixinPar name="s_resolution" alias="sResolution"
			description="Default should work for you"
			>\getParam{ssa_spaceRes}{NULL}/3600.</mixinPar>
		<mixinPar name="t_max" alias="tMax"
			description="Default should work for you"
			>ssa_dateObs+ssa_timeExt/43200.</mixinPar>
		<mixinPar name="t_min" alias="tMin"
			description="Default should work for you"
			>ssa_dateObs-ssa_timeExt/43200.</mixinPar>
		<mixinPar name="target_name" alias="targetName"
			description="Default should work for you"
			>ssa_targname</mixinPar>
		<mixinPar name="target_class" alias="targetClass"
			description="Default should work for you"
			>ssa_targclass</mixinPar>
		<mixinPar name="obs_title" alias="title"
			description="Default should work for you"
			>ssa_dstitle</mixinPar>
		<mixinPar name="em_ucd" alias="emUCD"
			description="Default should work for you"
			>\sqlquote{\getParam{ssa_spectralucd}}</mixinPar> 
		<mixinPar name="o_ucd" alias="oUCD"
			description="Default should work for you"
			>\sqlquote{\getParam{ssa_fluxucd}}</mixinPar> 
		<mixinPar name="em_res_power" alias="emResPower"
			description="Default should work for you"
			>ssa_specstart/ssa_specres</mixinPar> 
		<mixinPar name="obs_publisher_did" alias="did"
			description="Default should work for you."
			>ssa_pubdid</mixinPar>
		<mixinPar name="s_xel1"
			description="You shouldn't use SSA for cubes.">1</mixinPar>
		<mixinPar name="s_xel2"
			description="You shouldn't use SSA for cubes.">1</mixinPar>
	</mixinDef>

	<mixinDef id="publishObscoreLike">
		<doc>
			Publish a table that already has large parts of the obscore schema.

			This has parameters called like the corresponding obscore columns
			that also default to taking their data columns named like them.
			Use this when you already have, by and large, and obscore structure
			in your source table.
			
			Note that this will *not* do the right thing with product#table
			instances by default.  For these, you will have to manually
			map access_url, access_format, and access_estsize.
		</doc>

		<LOOP>
			<codeItems>
				for column in context.getById("ObsCore").columns:
					yield {'name': column.name, 'description': column.description}
			</codeItems>
			<events>
				<mixinPar name="\name"
					description="\description">\name</mixinPar>
			</events>
		</LOOP>
		<mixinPar name="source_table" description="The table this is mixed into
			(you probably don't want to change this)."
				>'\qName'</mixinPar>
		<mixinPar name="createDIDIndex" description="Index whatever expression
			you give for the dataset identifier?  You probably want this
			on tables with more than a few hundred datasets unless the DID is
			in a column that you already index anyway.">False</mixinPar>

		<FEED source="%#obscore-extrapars"/>

		<events>
			<adql>True</adql>
			<!-- the casts in the following table are there to keep postgres
			from inferring weird types when parts of the union have NULL
			entries-->
			<property name="obscoreClause">
						CAST(\dataproduct_type AS text) AS dataproduct_type,
						CAST(\dataproduct_subtype AS text) AS dataproduct_subtype,
						CAST(\calib_level AS smallint) AS calib_level,
						CAST(\obs_collection AS text) AS obs_collection,
						CAST(\obs_id AS text) AS obs_id,
						CAST(\obs_title AS text) AS obs_title,
						CAST(\obs_publisher_did AS text) AS obs_publisher_did,
						CAST(\obs_creator_did AS text) AS obs_creator_did,
						CAST(\access_url AS text) AS access_url,
						CAST(\access_format AS text) AS access_format,
						CAST(\access_estsize AS bigint) AS access_estsize,
						CAST(\target_name AS text) AS target_name,
						CAST(\target_class AS text) AS target_class,
						CAST(\s_ra AS double precision) AS s_ra,
						CAST(\s_dec AS double precision) AS s_dec,
						CAST(\s_fov AS double precision) AS s_fov,
						CAST(\s_region AS spoly) AS s_region,
						CAST(\s_resolution AS double precision) AS s_resolution,
						CAST(\t_min AS double precision) AS t_min,
						CAST(\t_max AS double precision) AS t_max,
						CAST(\t_exptime AS double precision) AS t_exptime,
						CAST(\t_resolution AS double precision) AS t_resolution,
						CAST(\em_min AS double precision) AS em_min,
						CAST(\em_max AS double precision) AS em_max,
						CAST(\em_res_power AS double precision) AS em_res_power,
						CAST(\o_ucd AS text) AS o_ucd,
						CAST(\pol_states AS text) AS pol_states,
						CAST(\facility_name AS text) AS facility_name,
						CAST(\instrument_name AS text) AS instrument_name,
						CAST(\s_xel1 AS bigint) AS s_xel1,
						CAST(\s_xel2 AS bigint) AS s_xel2,
						CAST(\t_xel AS bigint) AS t_xel,
						CAST(\em_xel AS bigint) AS em_xel,
						CAST(\pol_xel AS bigint) AS pol_xel,
						CAST(\s_pixel_scale AS double precision) AS s_pixel_scale,
						CAST(\em_ucd AS text) AS em_ucd,
						CAST(\preview AS text) AS preview,
						CAST(\source_table AS text) AS source_table
			</property>
			<FEED source="maintenance-scripts"/>
			<FEED source="%#obscore-extraevents"/>
		</events>

		<processLate original="obscore-processLate"/>
	</mixinDef>

	<table id="emptyobscore" onDisk="True" adql="hidden" system="True">
		<meta name="description">An empty table having all columns of the
		obscore table.  Useful internally, and sometimes for tricky queries.
		</meta>
		<mixin
			access_url="access_url"
			calib_level="calib_level"
			obs_collection="obs_collection"
			s_region="s_region"
			obs_creator_did="obs_creator_did"
			obs_publisher_did="obs_publisher_did"
			access_format="access_format"
			dataproduct_type="dataproduct_type"
			obs_id="obs_id"
			preview="NULL"
			access_estsize="access_estsize">publish</mixin>
		<FEED source="obscore-columns"/>
		<adql>hidden</adql>
	</table>

	<data id="makeSources">
		<make table="_obscoresources"/>
		<make table="emptyobscore"/>
	</data>

	<data id="create">
		<!-- the view is created from prescriptions in _obscoresources -->
		<make table="ObsCore">
			<script original="createObscoreView" type="postCreation"/>

			<script name="unpublish obscore DM" type="beforeDrop" lang="SQL">
					delete from tap_schema.supportedmodels
						where dmivorn='ivo://ivoa.net/std/ObsCore#core-1.1'
			</script>
		</make>
	</data>

	<data id="recover" updating="True" recreateAfter="create" auto="False">
		<!-- removes "bad" entries from obscoresources -->
		<sources items="cleanup"/>
		<nullGrammar/>
		<make table="_obscoresources">
			<script name="Clean bad entries from obscore sources"
					type="newSource" lang="python">
				q = base.UnmanagedQuerier(table.connection)

				# first, remove all tables that don't exist any more
				for tn, in q.connection.query(
						"select tablename from ivoa._obscoresources"):
					if not q.getTableType(tn):
						q.connection.execute("delete from ivoa._obscoresources where"
							" tablename=%(tn)s", locals())

				# then, remove tables with bad view contributions
				for tn, statement in q.connection.query(
						"select tablename, sqlfragment from ivoa._obscoresources"):
					with base.getUntrustedConn() as subconn:
						try:
							_ = list(subconn.query(statement+" LIMIT 1"))
						except base.DBError:
							q.connection.execute("delete from ivoa._obscoresources where"
							" tablename=%(tn)s", locals())
			</script>
		</make>
	</data>

	<data id="refreshAfterSchemaUpdate" auto="False" updating="True">
		<recreateAfter>create</recreateAfter>
		<sources item="dummy"/>
		<nullGrammar/>
		<make table="ObsCore">
			<script name="update all obscore definitions" type="newSource"
					lang="python">
				from gavo import rsc
				from gavo.rscdef import scripting

				def updateObscore(conn, tableDefs):
					obscoreRD = base.caches.getRD("//obscore")
					runner = scripting.PythonScriptRunner(
						obscoreRD.getById("addTableToObscoreSources"))

					for tableDef in tableDefs:
						base.ui.notifyNewSource(tableDef.getQName())
						depTable = rsc.TableForDef(tableDef, connection=conn)
						runner.run(depTable)
						base.ui.notifySourceFinished()
				
				if table.getTableType("ivoa._obscoresources") is not None:
					srcTables, mth = [], base.caches.getMTH(None)
					for r in table.connection.query(
							"select tablename from ivoa._obscoresources"):
						try:
							srcTables.append(mth.getTableDefForTable(r[0]))
						except base.RDNotFound as exc:
							base.ui.notifyWarning("While updating obscore: table"
								" %s declared in vanished RD %s purged from"
								" ivoa.obscore."%(r[0], exc.args[0]))
					updateObscore(table.connection, srcTables)
			</script>
		</make>
	</data>

	<service id="dl" allowed="dlmeta">
		<meta name="description">
			A datalink service accompanying obscore.  This will forward to data
			collection-specific datalink services if they exist or return extremely
			basic datalinks otherwise.
		</meta>
		
		<datalinkCore>
			<descriptorGenerator>
				<setup>
					<code>
						from gavo import svcs
						from gavo.protocols import products

						class ObscoreDescriptor(ProductDescriptor):
							def __init__(self, 
									pubDID, accessURL, accessSize,
									mime, preview, sourceTable):
								ProductDescriptor.__init__(self, pubDID, None,
									accessURL, mime,
									sourceTable=sourceTable, preview=preview)
								self.accessSize = accessSize
								self.suppressAutoLinks = True

						currentHost = base.getCurrentServerURL()
					</code>
				</setup>
				<code>
					with base.getTableConn() as conn:
						res = list(conn.queryToDicts(
							"SELECT"
							"   access_url, access_format, access_estsize,"
							" preview, source_table"
							" FROM ivoa.obscore"
							" WHERE obs_publisher_did=%(pubdid)s",
							{"pubdid": pubDID}))

						if not res:
							return DatalinkFault.NotFoundFault(pubDID,
								"No data set with pubDID %s known here"%pubDID)
						ocrow = res[0]

						td = base.getTableDefForTable(conn, ocrow["source_table"])
						dlid = base.getMetaText(td,
							"_associatedDatalinkService.serviceId", default=None)
						if dlid:
							dlsvc = td.rd.getById(dlid)
							dlurl = dlsvc.getURL("dlmeta")
							raise svcs.Found(dlurl+"?ID="+urllib.parse.quote(pubDID))

						return ObscoreDescriptor(
							pubDID,
							products.formatProductLink(ocrow["access_url"], currentHost),
							ocrow["access_estsize"]*1024,
							ocrow["access_format"],
							ocrow["preview"],
							ocrow["source_table"])
				</code>
			</descriptorGenerator>

			<metaMaker>
				<code>
					yield descriptor.makeLink(
						descriptor.accessPath,
						description="The main data product",
						semantics="#this",
						contentType=descriptor.mime,
						contentLength=descriptor.accessSize)

					if descriptor.preview:
						yield descriptor.makeLink(
							descriptor.preview,
							description="A preview or thumbnail of the data",
							semantics="#preview")
				</code>
			</metaMaker>
		</datalinkCore>
	</service>
</resource>
