Module:Constraints/SPARQL

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('%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__to ?should_link_via__toLabel WHERE {	?should_link_via__to wdt: ?item .	FILTER NOT EXISTS { ?item wdt: ?should_link_via__to } . } 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 ?sample2  ?exception WITH {	SELECT ?formatter WHERE {		OPTIONAL { wd: wdt:P1630 ?formatter }	} LIMIT 1 } AS %formatter WHERE {	{		SELECT ?item (COUNT(?value) AS ?count) (MIN(?value) AS ?sample1) (MAX(?value) AS ?sample2) {			?item p: [ ps: ?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: p:P2302 [ ps:P2302 wd:Q19474404; pq:P2303 ?exc ] .		FILTER( ?exc = ?item ) .	} .	BIND( BOUND( ?exc ) AS ?exception ) . } 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 ?sample2  ?exception WITH {	SELECT ?formatter WHERE {		OPTIONAL { wd: wdt:P1630 ?formatter }	} LIMIT 1 } AS %formatter WHERE {	{		SELECT ?item (COUNT(?value) AS ?count) (MIN(?value) AS ?sample1) (MAX(?value) AS ?sample2) {			?item wdt: ?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: p:P2302 [ ps:P2302 wd:Q52060874; pq:P2303 ?exc ] .		FILTER( ?exc = ?item ) .	} .	BIND( BOUND( ?exc ) AS ?exception ) . } 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:/ps: ?value .			MINUS { 				?value p:/ps: ?item .			} .		}		LIMIT 100	} . } 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 WHERE {	?item p: [ ?pq ?value; wikibase:rank ?rank ] .	?prop wikibase:qualifier ?pq .	FILTER( ?prop NOT IN ) . } 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 WHERE {	?item p: ?statement .	VALUES ?pq { } .	OPTIONAL {		?statement ?pq ?qualif .	} .	FILTER( !BOUND( ?qualif ) ) .	?prop wikibase:qualifier ?pq .	?statement wikibase:rank ?rank; ps: ?value . } 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 WHERE	{	{		SELECT ?item (COUNT(?value) AS ?count) (SAMPLE(?value) AS ?sample) {			?item wdt: ?value .		}		GROUP BY ?item		HAVING ( ?count = 1 )	} } 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 : report listing each item

SELECT DISTINCT ?item1 ?item1Label ?item2 ?item2Label ?value {	?item1 wdt: ?value. ?item2 wdt: ?value. FILTER( ?item1 != ?item2 && STR( ?item1 ) < STR( ?item2 ) ). } 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 : 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: ?value }   	GROUP BY ?value HAVING (?ct>1) ORDER BY DESC(?ct) LIMIT 100 } 	?item wdt: ?value. SERVICE wikibase:label { bd:serviceParam wikibase:language "". ?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: ?statement .        ?statement ps: ?value; wikibase:rank ?rank .        BIND("mainsnak" AS ?snak) .      } UNION {        ?statement pq: ?value; wikibase:rank ?rank .        ?item ?p1 ?statement .        BIND("qualifier" AS ?snak) .      } UNION {        ?ref pr: ?value .        ?statement prov:wasDerivedFrom ?ref; wikibase:rank ?rank .        ?item ?p2 ?statement .        BIND("reference" AS ?snak) .      } .      FILTER( ?value  IN  ) .    } LIMIT 100  } . } 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:/ps: ?value .			BIND(wd:Q54828448 AS ?scope) .			, Q54828449 = 			?statement pq: ?value .			?item ?p ?statement .			FILTER(?p != p:P1855) .			BIND(wd:Q54828449 AS ?scope) .			, Q54828450 = 			?ref pr: ?value .			?statement prov:wasDerivedFrom ?ref .			?item ?p ?statement .			BIND(wd:Q54828450 AS ?scope) .			, }	local pattern = SELECT ?item ?itemLabel ?value ?scopeLabel WHERE {	{		SELECT DISTINCT ?item ?value ?scope		WHERE		{		} LIMIT 100	} . } 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 Label WHERE {	{		SELECT DISTINCT ?item  {			?item wdt: [] .			MINUS {				VALUES ?classes {  } .				?item wdt:P279* ?classes .			} .			OPTIONAL {				?item wdt:  .			} .		} LIMIT 100	} . } 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 Label ?snak WHERE {	{		SELECT DISTINCT ?item ?value  ?snak {			{				?item wdt: ?value .				BIND("mainsnak" AS ?snak) .			} UNION {				?statement0 pq: ?value .				?item ?p0 ?statement0 .				BIND("qualifier" AS ?snak) .			} UNION {				?ref pr: ?value .				?statement1 prov:wasDerivedFrom ?ref .				?item ?p1 ?statement1 .				BIND("reference" AS ?snak) .			} .			MINUS {				VALUES ?classes {  } .							?value wdt:P279* ?classes .			} .			OPTIONAL {				?value wdt:  .			} .		} LIMIT 100	} . } 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.
 * 1) Limited to statements with best rank (wdt:), exceptions not filtered
 * 2) query added by Jura1, 2017-08-03

SELECT ?item ?itemLabel ?itemDescription ?value ?valueLabel WHERE {	?item wdt: ?value. FILTER NOT EXISTS { ?item ]] if #values == 0 then pattern = pattern .. " p: []" else pattern = pattern .. " wdt: " end pattern = pattern .. } } 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 : report listing each item and value

SELECT ?item ?itemLabel ?value ?valueLabel ?snak WHERE {	{		SELECT DISTINCT ?item ?value ?snak WHERE {			{				?item p:/ps: ?value. BIND("mainsnak" AS ?snak). } UNION { ?statement0 pq: ?value. ?item ?p0 ?statement0. BIND("qualifier" AS ?snak). } UNION { ?ref pr: ?value. ?statement1 prov:wasDerivedFrom ?ref. ?item ?p1 ?statement1. BIND("reference" AS ?snak). } .		} LIMIT 100 } . }]]	local block, export1, export2 if #values > 0 then block = processPattern(			VALUES ?values { } .			MINUS {				?value p:/ps: ?values .			} .			OPTIONAL {				?value p:/ps: ?target .			} .,			{				property = property,				values = values,			}) export1 = '?target ' export2 = '?targetLabel ' else block = processPattern('MINUS { ?value p: [] } .', {			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 : report by values used
 * 1) Limited to values in best rank (wdt), exceptions not filtered
 * 2) 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: ?value. MINUS { ?value p: [] } }		GROUP BY ?value ORDER BY DESC(?ct) ?value LIMIT 1000 } } 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) ?snak ?rank WHERE {	{		SELECT ?item ?value ?result ?snak ?rank		WHERE		{			{				?item p: [ ps: ?value; wikibase:rank ?rank ] .				BIND("mainsnak" AS ?snak) .			} UNION {				?statement1 pq: ?value;					wikibase:rank ?rank .				?item ?p1 ?statement1 .				BIND("qualifier" AS ?snak) .			} UNION {				?ref pr: ?value .				?statement2 prov:wasDerivedFrom ?ref;					wikibase:rank ?rank .				?item ?p2 ?statement2 .				BIND("reference" AS ?snak) .			} .			BIND( REGEX( STR( ?value ), "" ) AS ?regexresult ) .			FILTER( ?regexresult = false ) .			BIND( IF( ?regexresult = true, "pass", "fail" ) AS ?result ) .		} 		LIMIT 100	} . }	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
 * 1) ORDER BY ?rank ?snak ?value
 * 2) PLEASE NOTE: This is experimental and may only work for simple patterns.
 * 3) Tests may fail due to:
 * 4) (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!
 * 5) (2) some bug in the link that brought you here
 * 6) Known to fail: P227 (multiple curly braces), P274, P281]]

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: [] .			VALUES ?value { } .			?item wdt: ?value .			BIND( wd: AS ?property ) .		} LIMIT 100	} . } else pattern = SELECT ?item ?itemLabel ?property ?propertyLabel ?value ?valueLabel WHERE {	{		SELECT DISTINCT ?item ?property ?value {			?item wdt: []; wdt: ?value .			BIND( wd: AS ?property ) .		} LIMIT 100	} . } 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:/psv: [		wikibase:quantityAmount ?value;		wikibase:quantityLowerBound ?lower;		wikibase:quantityUpperBound ?upper	] .	BIND( ( ?upper - ?lower ) / 2 AS ?diff ) . } 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: ?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:              ?stmnode.        ?stmnode   psv:            ?valuenode.        ?valuenode wikibase:quantityUnit   ?unit.      }    }.    OPTIONAL {		wd: 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:/psv:/wikibase:quantityAmount ?value .	FILTER( xsd:integer( ?value ) != ?value ) . } local query = processPattern(pattern, {		id = _id,		sandbox = notSandbox,		service = getService,	}) return queryAsURL(query) end

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

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