Module:Constraints/SPARQL

Documentation for this module may be created at Module:Constraints/SPARQL/doc

local _lang = 'en'
local _id
local _datatype
local _fallback

local function getDatatype()
	if not _datatype then
		_datatype = mw.wikibase.getEntity(_id).datatype
	end
	return _datatype
end

local function getLanguageFallback(lang)
	if not _fallback then
		_fallback = mw.language.getFallbacksFor(lang)
		table.insert(_fallback, 1, lang)
	end
	return _fallback
end

local function getService()
	return string.format('SERVICE wikibase:label { bd:serviceParam wikibase:language "%s" } .',
		table.concat(getLanguageFallback(_lang), ','))
end

local function notSandbox()
	return 'FILTER( ?item NOT IN ( wd:Q4115189, wd:Q13406268, wd:Q15397819 ) ) .'
end

local function getSpecificData(var)
	local datatype = getDatatype()
	local vars = {}
	if datatype == 'monolingualtext' then
		table.insert(vars, '(LANG(' .. var .. ') AS ?lang)')
	end
	if datatype == 'wikibase-item' or datatype == 'wikibase-property' then
		table.insert(vars, var .. 'Label')
	end
	return table.concat(vars, ' ')
end

local function processCallback(data)
	return function(param)
		local split = mw.text.split(param, ';')
		local param, prefix, delim = split[1], split[2] or 'wd:', split[3] or ''
		if type(data[param]) == 'table' then
			return prefix .. table.concat(data[param], delim .. ' ' .. prefix)
		else
			return data[param]
		end
	end
end

local function processPattern(pattern, data)
	return string.gsub(pattern, '%{%{%{([^%}]+)%}%}%}', processCallback(data))
end

function queryAsURL(query, text)
	return string.format('[https://query.wikidata.org/#%s %s]', mw.uri.encode(query, 'PATH'), text or 'SPARQL')
end

local p = {}

function p.buildInverse(property)
	-- "property" could also be from statements?
	local pattern = [[SELECT ?item ?itemLabel ?should_link_via_{{{property}}}_to ?should_link_via_{{{property}}}_toLabel 
WHERE
{
	?should_link_via_{{{property}}}_to wdt:{{{id}}} ?item .
	FILTER NOT EXISTS { ?item wdt:{{{property}}} ?should_link_via_{{{property}}}_to } .
	{{{service}}}
}
LIMIT 100]]
	local query = processPattern(pattern, {
		id = _id,
		property = property,
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildSingleValue()
	local pattern = [[SELECT DISTINCT ?item ?itemLabel ?count ?sample1 {{{specific1}}} ?sample2 {{{specific2}}} ?exception
WITH {
	SELECT ?formatter WHERE {
		OPTIONAL { wd:{{{id}}} wdt:P1630 ?formatter }
	} LIMIT 1
} AS %formatter
WHERE
{
	{
		SELECT ?item (COUNT(?value) AS ?count) (MIN(?value) AS ?sample1) (MAX(?value) AS ?sample2) {
			?item p:{{{id}}} [ ps:{{{id}}} ?val; wikibase:rank ?rank ] .
			FILTER( ?rank != wikibase:DeprecatedRank ) .
			INCLUDE %formatter .
			BIND( IF( BOUND( ?formatter ), URI( REPLACE( ?formatter, '\\$1', ?val ) ), ?val ) AS ?value ) .
		} GROUP BY ?item HAVING ( ?count > 1 ) LIMIT 100
	} .
	OPTIONAL {
		wd:{{{id}}} p:P2302 [ ps:P2302 wd:Q19474404; pq:P2303 ?exc ] .
		FILTER( ?exc = ?item ) .
	} .
	BIND( BOUND( ?exc ) AS ?exception ) .
	{{{service}}}
}
ORDER BY DESC(?count)]]
	local query = processPattern(pattern, {
		id = _id,
		service = getService(),
		specific1 = getSpecificData('?sample1'),
		specific2 = getSpecificData('?sample2'),
	})
	return queryAsURL(query)
end

function p.buildSingleBestValue()
	local pattern = [[SELECT DISTINCT ?item ?itemLabel ?count ?sample1 {{{specific1}}} ?sample2 {{{specific2}}} ?exception
WITH {
	SELECT ?formatter WHERE {
		OPTIONAL { wd:{{{id}}} wdt:P1630 ?formatter }
	} LIMIT 1
} AS %formatter
WHERE
{
	{
		SELECT ?item (COUNT(?value) AS ?count) (MIN(?value) AS ?sample1) (MAX(?value) AS ?sample2) {
			?item wdt:{{{id}}} ?val .
			INCLUDE %formatter .
			BIND( IF( BOUND( ?formatter ), URI( REPLACE( ?formatter, '\\$1', ?val ) ), ?val ) AS ?value ) .
		} GROUP BY ?item HAVING ( ?count > 1 ) LIMIT 100
	} .
	OPTIONAL {
		wd:{{{id}}} p:P2302 [ ps:P2302 wd:Q52060874; pq:P2303 ?exc ] .
		FILTER( ?exc = ?item ) .
	} .
	BIND( BOUND( ?exc ) AS ?exception ) .
	{{{service}}}
}
ORDER BY DESC(?count)]]
	local query = processPattern(pattern, {
		id = _id,
		service = getService(),
		specific1 = getSpecificData('?sample1'),
		specific2 = getSpecificData('?sample2'),
	})
	return queryAsURL(query)
end

function p.buildSymmetric()
	local pattern = [[SELECT ?item ?itemLabel ?value ?valueLabel
WHERE
{
	{
		SELECT DISTINCT ?item ?value {
			?item p:{{{id}}}/ps:{{{id}}} ?value .
			MINUS { 
				?value p:{{{id}}}/ps:{{{id}}} ?item .
			} .
		}
		LIMIT 100
	} .
	{{{service}}}
}]]
	local query = processPattern(pattern, {
		id = _id,
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildQualifiers(list)
	local pattern = [[SELECT ?item ?itemLabel ?prop ?propLabel ?rank ?value {{{specific}}}
WHERE
{
	?item p:{{{id}}} [ ?pq ?value; wikibase:rank ?rank ] .
	?prop wikibase:qualifier ?pq .
	FILTER( ?prop NOT IN ( {{{list;wd:;,}}} ) ) .
	{{{service}}}
}
LIMIT 100]]
	local query = processPattern(pattern, {
		id = _id,
		list = list,
		service = getService(),
		specific = getSpecificData('?value'),
	})
	return queryAsURL(query)
end

function p.buildMandatoryQualifiers(list)
	-- todo: no qualifiers
	local pattern = [[SELECT ?item ?itemLabel ?prop ?propLabel ?rank ?value {{{specific}}}
WHERE
{
	?item p:{{{id}}} ?statement .
	VALUES ?pq { {{{list;pq:}}} } .
	OPTIONAL {
		?statement ?pq ?qualif .
	} .
	FILTER( !BOUND( ?qualif ) ) .
	?prop wikibase:qualifier ?pq .
	?statement wikibase:rank ?rank; ps:{{{id}}} ?value .
	{{{service}}}
}
LIMIT 100]]
	local query = processPattern(pattern, {
		id = _id,
		list = list,
		service = getService(),
		specific = getSpecificData('?value'),
	})
	return queryAsURL(query)
end

function p.buildMultiValue()
	local pattern = [[SELECT ?item ?itemLabel ?sample {{{specific}}}
WHERE	
{
	{
		SELECT ?item (COUNT(?value) AS ?count) (SAMPLE(?value) AS ?sample) {
			?item wdt:{{{id}}} ?value .
		}
		GROUP BY ?item
		HAVING ( ?count = 1 )
	}
	{{{service}}}
}
LIMIT 100]]
	local query = processPattern(pattern, {
		id = _id,
		service = getService(),
		specific = getSpecificData('?sample'),
	})
	return queryAsURL(query)
end

function p.buildUniqueValue()
	local pattern = [[#Unique value constraint report for {{{id}}}: report listing each item

SELECT DISTINCT ?item1 ?item1Label ?item2 ?item2Label ?value {{{specific}}}
{
	?item1 wdt:{{{id}}} ?value .
	?item2 wdt:{{{id}}} ?value .
	FILTER( ?item1 != ?item2 && STR( ?item1 ) < STR( ?item2 ) ) .
	{{{service}}}
}
LIMIT 100]]
	local query = processPattern(pattern, {
		id = _id,
		service = getService(),
		specific = getSpecificData('?value'),
	})
	return queryAsURL(query, 'SPARQL (every item)')
end

function p.buildUniqueValueByValue()
	local pattern = [[# Unique value constraint report for {{{id}}}: report by value

SELECT
    ?value (SAMPLE(?valueLabel) AS ?valueLabel) (SAMPLE(?ct) AS ?ct)
    (GROUP_CONCAT(DISTINCT(STRAFTER(STR(?item), "/entity/")); separator=", ") AS ?items)
    (GROUP_CONCAT(DISTINCT(?itemLabel); separator=", ") AS ?itemLabels)
WHERE    
{
  	{ 	SELECT ?value (COUNT(DISTINCT ?item) as ?ct)
  		WHERE
  		{ 
  			?item wdt:{{{id}}} ?value 
		}
    	GROUP BY ?value HAVING (?ct>1) 
    	ORDER BY DESC(?ct)
    	LIMIT 100
	}
  	?item wdt:{{{id}}} ?value .
	SERVICE wikibase:label {
    	bd:serviceParam wikibase:language "{{{lang;;,}}}" . 
    	?item rdfs:label ?itemLabel .
    	?value rdfs:label ?valueLabel .
  	}
}
GROUP BY ?value
ORDER BY DESC(?ct)]]
	local query = processPattern(pattern, {
		id = _id,
		lang = getLanguageFallback(_lang),
	})
	return queryAsURL(query, 'SPARQL (by value)')
end
	
function p.buildOneOf(values, neg)
	local pattern = [[SELECT ?item ?itemLabel ?value ?valueLabel ?snak ?rank ?statement
WHERE
{
  {
    SELECT ?item ?value ?result ?snak ?rank ?statement WHERE {
      {
        ?item p:{{{id}}} ?statement .
        ?statement ps:{{{id}}} ?value; wikibase:rank ?rank .
        BIND("mainsnak" AS ?snak) .
      } UNION {
        ?statement pq:{{{id}}} ?value; wikibase:rank ?rank .
        ?item ?p1 ?statement .
        BIND("qualifier" AS ?snak) .
      } UNION {
        ?ref pr:{{{id}}} ?value .
        ?statement prov:wasDerivedFrom ?ref; wikibase:rank ?rank .
        ?item ?p2 ?statement .
        BIND("reference" AS ?snak) .
      } .
      FILTER( ?value {{{NOT}}} IN ( {{{values;wd:;,}}} ) ) .
    } LIMIT 100
  } .
  {{{service}}}
}]]
	local query = processPattern(pattern, {
		id = _id,
		NOT = (neg and '') or 'NOT',
		service = getService(),
		values = values,
	})
	return queryAsURL(query)
end

function p.buildScope(scopes)
	-- todo: ?snak -> ?aspect with detail about "parent" property
	local map = {
		Q54828448 = [[
			?item p:{{{id}}}/ps:{{{id}}} ?value .
			BIND(wd:Q54828448 AS ?scope) .
			]],
		Q54828449 = [[
			?statement pq:{{{id}}} ?value .
			?item ?p ?statement .
			FILTER(?p != p:P1855) .
			BIND(wd:Q54828449 AS ?scope) .
			]],
		Q54828450 = [[
			?ref pr:{{{id}}} ?value .
			?statement prov:wasDerivedFrom ?ref .
			?item ?p ?statement .
			BIND(wd:Q54828450 AS ?scope) .
			]],
	}
	local pattern = [[SELECT ?item ?itemLabel ?value {{{specific}}} ?scopeLabel
WHERE
{
	{
		SELECT DISTINCT ?item ?value ?scope
		WHERE
		{
			{{{inside}}}
		} LIMIT 100
	} .
	{{{sandbox}}}
	{{{service}}}
}]]
	local inside = {}
	local ok = {}
	for _, scope in ipairs(scopes) do
		ok[scope] = true
	end
	for scope, pattern in pairs(map) do
		if not ok[scope] then
			table.insert(inside, pattern)
		end
	end
	if #inside == 1 then
		inside = processPattern(inside[1], { id = _id })
	else
		inside = processPattern('{\n' .. table.concat(inside, '} UNION {\n') .. '}', { id = _id })
	end
	local query = processPattern(pattern, {
		id = _id,
		inside = inside,
		sandbox = notSandbox(),
		service = getService(),
		specific = getSpecificData('?value'),
	})
	return queryAsURL(query)
end

function p.buildType(relationProp, classes)
	local pattern = [[SELECT ?item ?itemLabel {{{relationVar}}} {{{relationVar}}}Label
WHERE
{
	{
		SELECT DISTINCT ?item {{{relationVar}}} {
			?item wdt:{{{id}}} [] .
			MINUS {
				VALUES ?classes { {{{classes;wd:}}} } .
				?item {{{ifInstance}}}wdt:P279* ?classes .
			} .
			OPTIONAL {
				?item wdt:{{{relationProp}}} {{{relationVar}}} .
			} .
		} LIMIT 100
	} .
	{{{service}}}
}]]
	local vars = {
		P31 = '?instance',
		P279 = '?class',
	}
	local query = processPattern(pattern, {
		classes = classes,
		id = _id,
		ifInstance = (relationProp == 'P31' and 'wdt:P31/') or '',
		relationProp = relationProp,
		relationVar = vars[relationProp],
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildValueType(relationProp, classes)
	local pattern = [[SELECT ?item ?itemLabel ?value ?valueLabel {{{relationVar}}} {{{relationVar}}}Label ?snak 
WHERE
{
	{
		SELECT DISTINCT ?item ?value {{{relationVar}}} ?snak {
			{
				?item wdt:{{{id}}} ?value .
				BIND("mainsnak" AS ?snak) .
			} UNION {
				?statement0 pq:{{{id}}} ?value .
				?item ?p0 ?statement0 .
				BIND("qualifier" AS ?snak) .
			} UNION {
				?ref pr:{{{id}}} ?value .
				?statement1 prov:wasDerivedFrom ?ref .
				?item ?p1 ?statement1 .
				BIND("reference" AS ?snak) .
			} .
			MINUS {
				VALUES ?classes { {{{classes;wd:}}} } .			
				?value {{{ifInstance}}}wdt:P279* ?classes .
			} .
			OPTIONAL {
				?value wdt:{{{relationProp}}} {{{relationVar}}} .
			} .
		} LIMIT 100
	} .
	{{{service}}}
}]]
	local vars = {
		P31 = '?instance',
		P279 = '?class',
	}
	local query = processPattern(pattern, {
		classes = classes,
		id = _id,
		ifInstance = (relationProp == 'P31' and 'wdt:P31/') or '',
		relationProp = relationProp,
		relationVar = vars[relationProp],
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildRequiredClaim(property, values)
	if #values > 1 then
		return
	end
	local pattern = [[# Other properties generally found on  items with {{{id}}}. 
# Limited to statements with best rank (wdt:), exceptions not filtered
# query added by Jura1, 2017-08-03

SELECT ?item ?itemLabel ?itemDescription ?value ?valueLabel
WHERE
{
	?item wdt:{{{id}}} ?value .
	FILTER NOT EXISTS { ?item  ]]
	if #values == 0 then
		pattern = pattern .. " p:{{{property}}} []"
	else
		pattern = pattern .. " wdt:{{{property}}} {{{values}}}"
	end
	pattern = pattern .. [[ }
	{{{service}}}
}
LIMIT 1000]]

	local query = processPattern(pattern, {
		id = _id,
		property = property,
		values = values,
		service = getService(),
	})
	return queryAsURL(query, 'SPARQL')
end

function p.buildTargetRequiredClaim(property, values)
	local pattern = [[#Target required claim report for {{{id}}}: report listing each item and value

SELECT ?item ?itemLabel ?value ?valueLabel {{{export1}}}{{{export2}}}?snak
WHERE
{
	{
		SELECT DISTINCT ?item ?value {{{export1}}}?snak
		WHERE
		{
			{
				?item p:{{{id}}}/ps:{{{id}}} ?value .
				BIND("mainsnak" AS ?snak) .
			} UNION {
				?statement0 pq:{{{id}}} ?value .
				?item ?p0 ?statement0 .
				BIND("qualifier" AS ?snak) .
			} UNION {
				?ref pr:{{{id}}} ?value .
				?statement1 prov:wasDerivedFrom ?ref .
				?item ?p1 ?statement1 .
				BIND("reference" AS ?snak) .
			} .
			{{{block}}}
		} LIMIT 100
	} .
	{{{service}}}
}]]
	local block, export1, export2
	if #values > 0 then
		block = processPattern(
			[[VALUES ?values { {{{values;wd:}}} } .
			MINUS {
				?value p:{{{property}}}/ps:{{{property}}} ?values .
			} .
			OPTIONAL {
				?value p:{{{property}}}/ps:{{{property}}} ?target .
			} .]],
			{
				property = property,
				values = values,
			})
		export1 = '?target '
		export2 = '?targetLabel '
	else
		block = processPattern('MINUS { ?value p:{{{property}}} [] } .', {
			property = property
		})
		export1 = ''
		export2 = ''
	end
	local query = processPattern(pattern, {
		block = block,
		export1 = export1,
		export2 = export2,
		id = _id,
		property = property,
		service = getService(),
		values = values,
	})
	return queryAsURL(query)
end

function p.buildTargetRequiredClaimByValue(property, values)
	if #values > 0 then
		return
	end
	local pattern = [[#Target required claim report for {{{id}}}: report by values used
#Limited to values in best rank (wdt), exceptions not filtered
#Query added by Jura1, 2017-08-01; 2018-08-19

SELECT ?value ?valueLabel ?valueDescription ?ct
WHERE
{
	{
		SELECT ?value (COUNT(DISTINCT ?item) as ?ct)
		WHERE
		{
				?item wdt:{{{id}}} ?value .
				MINUS { ?value p:{{{property}}} [] }
		}
		GROUP BY ?value ORDER BY DESC(?ct) ?value  LIMIT 1000
	}
	{{{service}}}
}
ORDER BY DESC(?ct) ?value]]
	local query = processPattern(pattern, {
		id = _id,
		property = property,
		service = getService(),
	})
	return queryAsURL(query, 'SPARQL (by value)')
end

function p.buildFormat(regex)
	local pattern = [[SELECT ?item ?itemLabel ?value ?result (STRLEN(STR(?value)) AS ?stringlength) {{{specific}}} ?snak ?rank
WHERE
{
	{
		SELECT ?item ?value ?result ?snak ?rank
		WHERE
		{
			{
				?item p:{{{id}}} [ ps:{{{id}}} ?value; wikibase:rank ?rank ] .
				BIND("mainsnak" AS ?snak) .
			} UNION {
				?statement1 pq:{{{id}}} ?value;
					wikibase:rank ?rank .
				?item ?p1 ?statement1 .
				BIND("qualifier" AS ?snak) .
			} UNION {
				?ref pr:{{{id}}} ?value .
				?statement2 prov:wasDerivedFrom ?ref;
					wikibase:rank ?rank .
				?item ?p2 ?statement2 .
				BIND("reference" AS ?snak) .
			} .
			BIND( REGEX( STR( ?value ), "{{{regex}}}" ) AS ?regexresult ) .
			FILTER( ?regexresult = false ) .
			BIND( IF( ?regexresult = true, "pass", "fail" ) AS ?result ) .
			{{{not_sandbox}}}
		} 
		LIMIT 100
	} .
	{{{service}}}
}
#ORDER BY ?rank ?snak ?value
#PLEASE NOTE: This is experimental and may only work for simple patterns.
#Tests may fail due to:
#(1) differences in regex format between SPARQL (https://www.w3.org/TR/xpath-functions/#regex-syntax) and PCRE (used by constraint reports). Don't change the regex to work with SPARQL!
#(2) some bug in the link that brought you here
#Known to fail: P227 (multiple curly braces), P274, P281]]
	local PatternConv = require 'Module:PatternConv'
	local query = processPattern(pattern, {
		id = _id,
		not_sandbox = notSandbox(),
		regex = PatternConv._replace2({ source = regex }),
		service = getService(),
		specific = getSpecificData('?value'),
	})
	return queryAsURL(query)
end

function p.buildConflictsWith(property, values)
	local pattern
	if #values > 0 then
		pattern = [[SELECT ?item ?itemLabel ?property ?propertyLabel ?value ?valueLabel
WHERE
{
	{
		SELECT DISTINCT ?item ?property ?value {
			?item wdt:{{{id}}} [] .
			VALUES ?value { {{{values;wd:}}} } .
			?item wdt:{{{property}}} ?value .
			BIND( wd:{{{property}}} AS ?property ) .
		} LIMIT 100
	} .
	{{{service}}}
}]]
	else
		pattern = [[SELECT ?item ?itemLabel ?property ?propertyLabel ?value ?valueLabel
WHERE
{
	{
		SELECT DISTINCT ?item ?property ?value {
			?item wdt:{{{id}}} []; wdt:{{{property}}} ?value .
			BIND( wd:{{{property}}} AS ?property ) .
		} LIMIT 100
	} .
	{{{service}}}
}]]
	end
	local query = processPattern(pattern, {
		id = _id,
		property = property,
		values = values,
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildNoBounds()
	-- todo: other scopes
	local pattern = [[
SELECT ?item ?itemLabel ?value ?lower ?upper ?diff WHERE {
	?item p:{{{id}}}/psv:{{{id}}} [
		wikibase:quantityAmount ?value;
		wikibase:quantityLowerBound ?lower;
		wikibase:quantityUpperBound ?upper
	] .
	BIND( ( ?upper - ?lower ) / 2 AS ?diff ) .
	{{{sandbox}}}
	{{{service}}}
}]]
	local query = processPattern(pattern, {
		id = _id,
		sandbox = notSandbox(),
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildContemporary()
	local pattern = [[
SELECT DISTINCT ?subject ?value WHERE {
  ?subject wdt:{{{id}}} ?value .
  OPTIONAL { ?subject p:P569/psv:P569 [ wikibase:timeValue ?subject_birth ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?value p:P569/psv:P569 [ wikibase:timeValue ?value_birth ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?subject p:P571/psv:P571 [ wikibase:timeValue ?subject_inception ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?value p:P571/psv:P571 [ wikibase:timeValue ?value_inception ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?subject p:P580/psv:P580 [ wikibase:timeValue ?subject_start ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?value p:P580/psv:P580 [ wikibase:timeValue ?value_start ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?subject p:P570/psv:P570 [ wikibase:timeValue ?subject_death ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?value p:P570/psv:P570 [ wikibase:timeValue ?value_death ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?subject p:P576/psv:P576 [ wikibase:timeValue ?subject_dissolution ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?value p:P576/psv:P576 [ wikibase:timeValue ?value_dissolution ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?subject p:P582/psv:P582 [ wikibase:timeValue ?subject_end ; wikibase:timePrecision "11"^^xsd:integer ] . }
  OPTIONAL { ?value p:P582/psv:P582 [ wikibase:timeValue ?value_end ; wikibase:timePrecision "11"^^xsd:integer ] . }
  FILTER ((!BOUND(?subject_birth) || ((!BOUND(?value_death) || ?subject_birth>?value_death) && (!BOUND(?value_dissolution) || ?subject_birth>?value_dissolution) && (!BOUND(?value_end) || ?subject_birth>?value_end))) && (!BOUND(?value_birth) || ((!BOUND(?subject_death) || ?value_birth>?subject_death) && (!BOUND(?subject_dissolution) || ?value_birth>?subject_dissolution) && (!BOUND(?subject_end) || ?value_birth>?subject_end))) && (!BOUND(?subject_inception) || ((!BOUND(?value_death) || ?subject_inception>?value_death) && (!BOUND(?value_dissolution) || ?subject_inception>?value_dissolution) && (!BOUND(?value_end) || ?subject_inception>?value_end))) && (!BOUND(?value_inception) || ((!BOUND(?subject_death) || ?value_inception>?subject_death) && (!BOUND(?subject_dissolution) || ?value_inception>?subject_dissolution) && (!BOUND(?subject_end) || ?value_inception>?subject_end))) && (!BOUND(?subject_start) || ((!BOUND(?value_death) || ?subject_start>?value_death) && (!BOUND(?value_dissolution) || ?subject_start>?value_dissolution) && (!BOUND(?value_end) || ?subject_start>?value_end))) && (!BOUND(?value_start) || ((!BOUND(?subject_death) || ?value_start>?subject_death) && (!BOUND(?subject_dissolution) || ?value_start>?subject_dissolution) && (!BOUND(?subject_end) || ?value_start>?subject_end))) && ((BOUND(?subject_birth) && (BOUND(?value_death) || BOUND(?value_dissolution) || BOUND(?value_end))) || (BOUND(?value_birth) && (BOUND(?subject_death) || BOUND(?subject_dissolution) || BOUND(?subject_end))) || (BOUND(?subject_inception) && (BOUND(?value_death) || BOUND(?value_dissolution) || BOUND(?value_end))) || (BOUND(?value_inception) && (BOUND(?subject_death) || BOUND(?subject_dissolution) || BOUND(?subject_end))) || (BOUND(?subject_start) && (BOUND(?value_death) || BOUND(?value_dissolution) || BOUND(?value_end))) || (BOUND(?value_start) && (BOUND(?subject_death) || BOUND(?subject_dissolution) || BOUND(?subject_end)))))
}
LIMIT 200]]
	local query = processPattern(pattern, {
		id = _id,
	})
	return queryAsURL(query)
end

function p.buildUnits()
	-- todo: other scopes
	local pattern = [[
SELECT ?item ?unit ?exception
WHERE
{
	{
      SELECT ?item ?unit
      WHERE
      {
        ?item      p:{{{id}}}              ?stmnode.
        ?stmnode   psv:{{{id}}}            ?valuenode.
        ?valuenode wikibase:quantityUnit   ?unit.
      }
    }.
    OPTIONAL {
		wd:{{{id}}} p:P2302 [ ps:P2302 wd:Q21514353; pq:P2303 ?exc ] .
		FILTER( ?exc = ?item ) .
	} .
	BIND( BOUND( ?exc ) AS ?exception ) .
}]]
	local query = processPattern(pattern, {
		id = _id,
	})
	return queryAsURL(query)
end

function p.buildInteger()
	-- todo: other scopes
	local pattern = [[
SELECT ?item ?itemLabel ?value WHERE {
	?item p:{{{id}}}/psv:{{{id}}}/wikibase:quantityAmount ?value .
	FILTER( xsd:integer( ?value ) != ?value ) .
	{{{sandbox}}}
	{{{service}}}
}]]
	local query = processPattern(pattern, {
		id = _id,
		sandbox = notSandbox(),
		service = getService(),
	})
	return queryAsURL(query)
end

function p.buildNewSPARQL(uuid)
	local pattern = [[
# Note: before https://phabricator.wikimedia.org/T201150 is fixed, the result will only be partial
SELECT DISTINCT ?item ?itemLabel ?statement WHERE {
	?statement wikibase:hasViolationForConstraint wds:{{{constraint}}} .
	?item ?p ?statement .
	{{{sandbox}}}
	{{{service}}}
}
]]
	local query = processPattern(pattern, {
		constraint = table.concat(mw.text.split(uuid, '$', true), '-'),
		sandbox = notSandbox(),
		service = getService(),
	})
	return '<span title="before T201150 is fixed, the result will only be partial.">' .. queryAsURL(query, 'SPARQL (new)') .. '</span>'
end

function p.setLanguage(lang)
	_lang = lang
	_fallback = nil
end

function p.setId(id)
	_id = id
	_datatype = nil
end

function p.setDatatype(datatype)
	_datatype = datatype
end

return p