Smalyshev has uploaded a new change for review.
https://gerrit.wikimedia.org/r/183317
Change subject: 0.9 port phase 2 - data import works now
......................................................................
0.9 port phase 2 - data import works now
Change-Id: Ic07366dc430925180632b17d51af03564727a53a
---
A src/main/groovy/org/wikidata/gremlin/DateUtils.groovy
M src/main/groovy/org/wikidata/gremlin/Loader.groovy
M src/main/groovy/org/wikidata/gremlin/Schema.groovy
3 files changed, 172 insertions(+), 37 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/wikidata/gremlin
refs/changes/17/183317/1
diff --git a/src/main/groovy/org/wikidata/gremlin/DateUtils.groovy
b/src/main/groovy/org/wikidata/gremlin/DateUtils.groovy
new file mode 100644
index 0000000..47cf3f3
--- /dev/null
+++ b/src/main/groovy/org/wikidata/gremlin/DateUtils.groovy
@@ -0,0 +1,113 @@
+package org.wikidata.gremlin
+import org.joda.time.*;
+import org.joda.time.format.*;
+class DateUtils {
+ // Rough number of seconds in a year, for storing whole years
+ // Source: https://en.wikipedia.org/wiki/Year#Astronomical_years
+ public final static long SECONDS_IN_YEAR = 31557600L;
+ // Largest year that Java can represent accurately.
+ // This is just an threshold, beyond this year we do not try to parse
dates
+ // but store (number of whole years)*SECONDS_IN_YEAR.
+ public final static long LARGEST_YEAR = 292000000L;
+ // Parser for year as dates
+ private final static def yearParser =
DateTimeFormat.forPattern('y').withZone(DateTimeZone.UTC)
+ // Seconds for largest Java-representable date
+ public final static long LARGEST_DATE_SECONDS =
yearParser.parseDateTime(LARGEST_YEAR as String).getMillis()/1000;
+ // Seconds for lower bound of dates we represent as Java dates
+ public final static long LOWEST_DATE_SECONDS =
yearParser.parseDateTime("0").getMillis()/1000;
+
+ public final static DateTime dateMax = new
DateTime(LARGEST_DATE_SECONDS*1000+1, DateTimeZone.UTC)
+ public final static DateTime dateMin = new
DateTime(LOWEST_DATE_SECONDS*1000-1, DateTimeZone.UTC)
+
+ /**
+ * Can this date be converted to Java date?
+ * @param seconds
+ * @return
+ */
+ public static boolean javaDate(long seconds)
+ {
+ return seconds < LARGEST_DATE_SECONDS && seconds >
LOWEST_DATE_SECONDS;
+ }
+
+ /**
+ * Create Java DateTime object from seconds
+ * @param seconds
+ * @return DateTime
+ */
+ public static DateTime toJavaDate(long seconds) {
+ if(!javaDate(seconds)) {
+ /* TODO: not sure what to return here, maybe exception?
*/
+ return seconds>0?dateMax:dateMin;
+ }
+ return new DateTime(seconds*1000, DateTimeZone.UTC)
+ }
+
+ /**
+ * Get seconds from year (assuming it is YYYY-01-01 00:00:00)
+ * @param year
+ * @return
+ */
+ public static long fromYear(long year) {
+ if(year >= LARGEST_YEAR) {
+ return
SECONDS_IN_YEAR*(year-LARGEST_YEAR)+LARGEST_DATE_SECONDS;
+ }
+ if(year <= 0) {
+ return SECONDS_IN_YEAR*year+LOWEST_DATE_SECONDS;
+ }
+ yearParser.parseDateTime(year as String).getMillis()/1000
+ }
+
+ /**
+ * Translate from seconds to year
+ * Only to be used for non-Java date years
+ * @param seconds
+ * @return
+ */
+ public static long asYear(long seconds) {
+ if(seconds > 0) {
+ return
(seconds-LARGEST_DATE_SECONDS)/SECONDS_IN_YEAR+LARGEST_YEAR;
+ }
+ return (seconds-LOWEST_DATE_SECONDS)/SECONDS_IN_YEAR;
+ }
+
+ private final static def df = DateTimeFormat.forPattern('y-M-d')
+ /**
+ * Get seconds from date YYYY-MM-DD
+ * This makes sense only within gregorian calendar times
+ * @param isoDate
+ * @return
+ */
+ public static long fromDate(String isoDate) {
+ return fromDate(df.parseDateTime(isoDate))
+ }
+
+ /**
+ * Get seconds from Java date
+ * @param isoDate
+ * @return
+ */
+ public static long fromDate(Date javaDate) {
+ return javaDate.getTime()/1000;
+ }
+
+ /**
+ * Get seconds from Java date
+ * @param isoDate
+ * @return
+ */
+ public static long fromDate(DateTime javaDate) {
+ return javaDate.getMillis()/1000;
+ }
+
+ private final static def printForm =
ISODateTimeFormat.dateTimeNoMillis().withZone(DateTimeZone.UTC)
+ /**
+ * Returns ISO8601 (extended) representation of the date
+ * @return String
+ */
+ public static String asString(long seconds) {
+ if(!javaDate(seconds)) {
+ return sprintf("%04d-01-01T00:00:00Z", asYear(seconds));
+ }
+ return printForm.print(new DateTime(seconds*1000,
DateTimeZone.UTC))
+ }
+}
diff --git a/src/main/groovy/org/wikidata/gremlin/Loader.groovy
b/src/main/groovy/org/wikidata/gremlin/Loader.groovy
index 60aa7e2..04323e0 100644
--- a/src/main/groovy/org/wikidata/gremlin/Loader.groovy
+++ b/src/main/groovy/org/wikidata/gremlin/Loader.groovy
@@ -211,6 +211,14 @@
}
/**
+ * Is this data type a link?
+ */
+ private boolean isLinkType(datatype)
+ {
+ return datatype == 'wikibase-item' || datatype == 'wikibase-property'
+ }
+
+ /**
* Check if property exists and create it if not
* Creates endge labels, properties, etc.
*/
@@ -221,8 +229,7 @@
// For value properties, we also need the value prop
initProperty(getValueName(item['id']), getDataType(item['datatype']),
item['id'])
initProperty(getQualifierName(item['id']),
getDataType(item['datatype']), item['id'])
- if(item['datatype'] && item['datatype'] != 'wikibase-item'
- && item['datatype'] != 'wikibase-property' &&
item['datatype'] != 'string') {
+ if(item['datatype'] && !isLinkType(item['datatype']) &&
item['datatype'] != 'string') {
initProperty(getAllValuesName(getValueName(item['id'])))
initProperty(getAllValuesName(getQualifierName(item['id'])))
}
@@ -236,7 +243,7 @@
def v = bgraph.getVertex(id)
if(!v) {
v = bgraph.addVertex(id)
- v.setProperty("stub", true)
+ v.property("stub", true)
}
return v
}
@@ -368,15 +375,23 @@
log.info "Cannot update in batch mode, skipping..."
return
}
- for(cl in v.outE.has('edgeType', 'claim')) {
+ for(cl in v.outE('claim')) {
if(!(cl.contentHash in claimsById)) {
- def prop = cl.getProperty('property')
- def target = cl.inV().next()
+ // This one is not there anymore, remove it
+ def prop = cl.property('property').value()
+ def targetId = null
log.debug "Dropping old claim ${cl.wikibaseId}"
- v.outE.has('contentHash',
cl.contentHash).remove()
+ if(isLinkType(cl.property('datatype').value()))
{
+ // Try to find target id for this link
+ def e = v.outE(prop).has('contentHash',
cl.contentHash).next()
+ targetId =
e.property(getValueName(prop)).value()
+ }
+ // Drop related claims
+ v.outE('claim').has('contentHash',
cl.contentHash).remove()
+ v.outE(prop).has('contentHash',
cl.contentHash).remove()
// update also the links
- if(target.wikibaseId && target.wikibaseId[0] ==
'Q'
- && !v.out(prop).has('wikibaseId',
target.wikibaseId).hasNext()) {
+ if(targetId && targetId[0] == 'Q'
+ && !v.out(prop).has('wikibaseId',
targetId).hasNext()) {
def lname = getLinkName(prop)
// if this link targeted v(target) and
there are no links to it anymore
// drop it from links
@@ -386,7 +401,7 @@
break;
}
}
- v.setProperty(getLinkName(prop)+"_",
v[lname].join(' '))
+ v.singleProperty(getLinkName(prop)+"_",
v[lname].join(' '))
}
} else {
claimsById[cl.contentHash].exists = true
@@ -426,8 +441,8 @@
}
def value = extractPropertyValueFromClaim(qitem)
if(value) {
- item.setProperty(qname, value)
- if(qitem.datatype != 'wikibase-item' &&
qitem.datatype != 'string' && qitem.datatype != 'wikibase-property') {
+ item.property(qname, value)
+ if(!isLinkType(qitem.datatype) &&
qitem.datatype != 'string') {
addAllValues(qitem.datavalue.value,
qname, item)
}
}
@@ -509,7 +524,7 @@
}
v[it.key] = it.value
}
- item.setProperty(getAllValuesName(property), v)
+ item.property(getAllValuesName(property), v)
}
/**
@@ -525,34 +540,39 @@
def data = claim.mainsnak
def value = extractPropertyValueFromClaim(data)
def outgoing = getTargetFromSnak(claim.mainsnak, value)
- def isValue = (data.datatype != 'wikibase-item' && data.datatype !=
'wikibase-property')
+ def isValue = !isLinkType(data.datatype)
+ def dtype = data.datatype ?: data.snaktype ?: ""
+ log.debug("Processing claim {} with {}", claim.wikibaseId, data)
+
if(!outgoing) {
- return null
+ log.debug "Could not find target, skipping..."
+ return null
}
claims++;
// Here is the strange thing: v.outE() is slow but v.outE('claim') is
fast even though they return the same
// So we provide edge for all claims so we could fetch them e.g. for
deleting old ones
- // TODO: may not be needed anymore if we can use contentHash + property
-// def claimC = v.addEdge('claim', outgoing)
-// claimC.setProperty('wikibaseId', claim.id)
-// claimC.setProperty('property', data.property)
-// claimC.setProperty('contentHash', contentHash)
-
+ def claimC = v.addEdge('claim', outgoing)
+ claimC.property('wikibaseId', claim.id)
+ claimC.property('property', data.property)
+ claimC.property('contentHash', contentHash)
+ claimC.property('datatype', dtype)
+
log.debug "Updating claim ${claim.id}"
def claimE = v.addEdge(data.property, outgoing)
- claimE.setProperty('edgeType', 'claim')
- claimE.setProperty('contentHash', contentHash)
- claimE.setProperty('rank', claim.rank == "preferred")
- claimE.setProperty('wikibaseId', claim.id)
- claimE.setProperty('property', data.property)
-
+ claimE.property('edgeType', 'claim')
+ claimE.property('contentHash', contentHash)
+ claimE.property('rank', claim.rank == "preferred")
+ claimE.property('wikibaseId', claim.id)
+ claimE.property('property', data.property)
+ claimE.property('datatype', dtype)
+
if(claim.qualifiers) {
addQualifiers(claimE, claim.qualifiers)
}
if(isValue && value) {
- claimE.setProperty(getValueName(data.property), value)
+ claimE.property(getValueName(data.property), value)
if(data.datatype != 'string' ) {
addAllValues(data.datavalue.value,
getValueName(data.property), claimE)
}
@@ -561,7 +581,7 @@
// Since vertex and its edges are stored together but going to
other side
// requires loading another vertex
if(!isValue && outgoing.wikibaseId) {
- claimE.setProperty(getValueName(data.property),
outgoing.wikibaseId)
+ claimE.property(getValueName(data.property),
outgoing.wikibaseId)
}
}
@@ -581,10 +601,10 @@
if(!(outgoing.wikibaseId in linkCache[lname])) {
// We need the check above since despite being called SET,
the property
// would not accept the same value twice
- v.addProperty(lname, outgoing.wikibaseId)
+ v.property(lname, outgoing.wikibaseId)
linkCache[lname] << outgoing.wikibaseId
// This is the same in a form of a string, for ES to index since ES
does not index SET properties
- v.setProperty(lname+"_", linkCache[lname].join(' '))
+ v.property(lname+"_", linkCache[lname].join(' '))
}
}
@@ -614,11 +634,11 @@
case 'wikibase-property': return
allowLink?"P"+value['numeric-id']:null
case 'wikibase-item': return
allowLink?"Q"+value['numeric-id']:null
default:
- log.info "Unknown datatype on
${getCurrentVertex()?.wikibaseId}: ${claim.datatype}. Skipping."
+ log.warn "Unknown datatype on
${getCurrentVertex()?.wikibaseId}: ${claim.datatype}. Skipping."
return null
}
} catch(Exception e) {
- log.info "Value parsing failed on
${getCurrentVertex()?.wikibaseId}: ${claim.datatype} with: $e"
+ log.warn "Value parsing failed on
${getCurrentVertex()?.wikibaseId}: ${claim.datatype} with: $e"
return null
}
}
@@ -717,7 +737,9 @@
def rank = s.addProperty(mgmt, 'rank', Boolean.class)
def hash = s.addProperty(mgmt, "contentHash", String.class)
def etype = s.addProperty(mgmt, 'edgeType', String.class)
- def label = s.addEdgeLabel(mgmt, name, rank, wikibaseId, hash, etype)
+ def datatype = addProperty(mgmt, 'datatype', String.class)
+ def label = s.addEdgeLabel(mgmt, name, rank, wikibaseId, hash, etype,
datatype)
+
s.addVIndex(mgmt, label, "by_rank"+name, rank)
s.addVIndex(mgmt, label, "by_hash"+name, hash, wikibaseId)
diff --git a/src/main/groovy/org/wikidata/gremlin/Schema.groovy
b/src/main/groovy/org/wikidata/gremlin/Schema.groovy
index b6aac20..ddc67db 100644
--- a/src/main/groovy/org/wikidata/gremlin/Schema.groovy
+++ b/src/main/groovy/org/wikidata/gremlin/Schema.groovy
@@ -39,7 +39,7 @@
def wikibaseId = addProperty(mgmt, 'wikibaseId', String.class)
def vid = addProperty(mgmt, 'vid', String.class)
def type = addProperty(mgmt, 'type', String.class)
- addProperty(mgmt, 'datatype', String.class)
+ def datatype = addProperty(mgmt, 'datatype', String.class)
addProperty(mgmt, 'stub', Boolean.class)
addProperty(mgmt, 'lastrevid', Integer.class)
// There are two nodes with the special_value_node parameter: 'unknown',
'no_value'. They are used to
@@ -50,7 +50,7 @@
def etype = addProperty(mgmt, 'edgeType', String.class)
def rank = addProperty(mgmt, 'rank', Boolean.class)
def hash = addProperty(mgmt, "contentHash", String.class)
- def claims = addEdgeLabel(mgmt, 'claim', prop, wikibaseId, hash)
+ def claims = addEdgeLabel(mgmt, 'claim', prop, wikibaseId, hash, datatype)
addIndex(mgmt, 'by_wikibaseId', Vertex.class, [wikibaseId], true)
addIndex(mgmt, 'by_wikibaseIdE', Edge.class, [wikibaseId]) // may not be
needed
@@ -63,7 +63,7 @@
addIndex(mgmt, 'by_Ehash', Edge.class, [hash])
// ??? Maybe not needed anymore
- //addVIndex(mgmt, claims, 'by_claims', rank, prop, hash)
+ addVIndex(mgmt, claims, 'by_claims', rank, prop, hash)
// Mixed indexes
// Index for P123link values - using P123link_ for now, no direct support
for SET properties
--
To view, visit https://gerrit.wikimedia.org/r/183317
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic07366dc430925180632b17d51af03564727a53a
Gerrit-PatchSet: 1
Gerrit-Project: wikidata/gremlin
Gerrit-Branch: master
Gerrit-Owner: Smalyshev <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits