package org.aksw.sparqlify.automapper;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.aksw.commons.util.jdbc.Column;
import org.aksw.commons.util.jdbc.ColumnsReference;
import org.aksw.commons.util.jdbc.ForeignKey;
import org.aksw.commons.util.jdbc.JdbcUtils;
import org.aksw.commons.util.jdbc.PrimaryKey;
import org.aksw.commons.util.jdbc.Relation;
import org.aksw.commons.util.jdbc.Schema;
import org.aksw.commons.util.strings.StringUtils;
import org.aksw.jena_sparql_api.exprs_ext.E_StrConcatPermissive;
import org.aksw.jena_sparql_api.views.E_RdfTerm;
import org.aksw.jena_sparql_api.views.VarDef;
import org.aksw.sparqlify.algebra.sql.nodes.SqlOpQuery;
import org.aksw.sparqlify.config.syntax.ViewDefinition;
import org.aksw.sparqlify.core.DatatypeSystemDefault;
import org.aksw.sparqlify.core.DatatypeSystemOld;
import org.aksw.sparqlify.core.SqlDatatype;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.sdb.core.Generator;
import org.apache.jena.sdb.core.Gensym;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprList;
import org.apache.jena.sparql.expr.ExprVar;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.XSD;
import org.postgresql.ds.PGSimpleDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Multimap;
interface UriSchema {
String getClassUri(String relationName);
}
class UriSchemaDefault
implements UriSchema
{
private String hostName;
public UriSchemaDefault(){
hostName = "http://localhost";
}
public String getNamespace()
{
return hostName;
}
@Override
public String getClassUri(String relationName) {
return getNamespace() + "/" + relationName;
}
}
public class AutoMapper {
public static final Logger logger = LoggerFactory.getLogger(AutoMapper.class);
public static String getBlockKey(String key) {
return key.replace("_", "").toLowerCase();
}
/**
* Foreign keys by convention
*
* @return
*/
/*
public static Multimap<String, ForeignKey> getConventionalForeignKeys(Collection<RelationMetadata> relations) {
Map<String, RelationMetadata> relationBlockKeys = new HashMap<String, RelationMetadata>();
for(RelationMetadata relation : relations) {
relationBlockKeys.put(getBlockKey(relation.getName()), relation);
}
for(RelationMetadata relation : relations) {
String relationNameBlockKey = getBlockKey(relation.getName());
for(ColumnMetadata column : relation.getColumns().values()) {
// TODO person_id ->
if(!column.getName().endsWith("id")) {
continue;
}
String columnNamePrefix = column.getName().substring(0, column.getName().length() - 3);
String blockName = getBlockKey(columnNamePrefix);
RelationMetadata target = relationBlockKeys.get(columnNamePrefix);
if(target == null) {
continue;
}
// check if
}
}
}*/
public static List<Column> getNonKeyColumns(Relation relation, PrimaryKey primaryKey, Collection<ForeignKey> fks) {
List<Column> result = new ArrayList<Column>();
for(Column column : relation.getColumns().values()) {
String name = column.getName();
if(primaryKey != null) {
if(primaryKey.getSource().getColumnNames().contains(name)) {
continue;
}
}
for(ForeignKey fk : fks) {
if(fk.getSource().getColumnNames().contains(name)) {
continue;
}
}
result.add(column);
}
return result;
}
public static VarDef columnReferenceToUriTermDef(String prefix, ColumnsReference columnsReference) {
return new VarDef(columnReferenceToUriExpr(prefix, columnsReference));
}
public static Expr columnReferenceToUriExpr(String prefix, ColumnsReference columnsReference) {
return E_RdfTerm.createUri(columnReferenceToExpr(prefix, columnsReference));
}
public static Expr columnReferenceToExpr(String prefix, ColumnsReference columnsReference) {
ExprList exprs = new ExprList();
prefix += columnsReference.getTableName();
Expr prefixExpr = NodeValue.makeString(prefix);
exprs.add(prefixExpr);
List<String> columnNames = columnsReference.getColumnNames();
for(int i = 0; i < columnNames.size(); ++i) {
if(i != 0) {
exprs.add(NodeValue.makeString("-"));
}
exprs.add(new ExprVar(columnNames.get(i)));
}
E_StrConcatPermissive result = new E_StrConcatPermissive(exprs);
return result;
}
public static void main(String[] args) throws SQLException {
// Read commandline args (database connection + optional file with prefix definitions)
/*
List<Node> nodes = new ArrayList<Node>();
nodes.add(RDF.type.asNode());
nodes.add(RDF.first.asNode());
nodes.add(RDF.rest.asNode());
Query q = CannedQueryUtils.selectBySubjects(nodes);
System.out.println(q);
if(true) {
return;
}
*/
AutoMapper mapper = new AutoMapper();
//mapper.generate(getConnection());
DatatypeSystemOld system = new DatatypeSystemDefault();
Map<String, SqlDatatype> datatypeMap = new HashMap<String, SqlDatatype>();
datatypeMap.put("int4", DatatypeSystemDefault._INT);
datatypeMap.put("varchar", DatatypeSystemDefault._STRING);
datatypeMap.put("bpchar", DatatypeSystemDefault._STRING);
datatypeMap.put("text", DatatypeSystemDefault._STRING);
datatypeMap.put("date", DatatypeSystemDefault._DATE);
datatypeMap.put("float8", DatatypeSystemDefault._FLOAT);
datatypeMap.put("timestamp", DatatypeSystemDefault._DATE_TIME);
Schema schema = Schema.create(getConnection());
Set<String> relationNames = JdbcUtils.fetchRelationNames(getConnection());
System.out.println(relationNames);
Map<String, Relation> relations = JdbcUtils.fetchColumns(getConnection());
Multimap<String, ForeignKey> foreignKeys = schema.getForeignKeys();
System.out.println(foreignKeys);
Map<String, PrimaryKey> primaryKeys = schema.getPrimaryKeys();
System.out.println(primaryKeys);
// Infer Foreign keys
// This method is good enough for BSBM, but could be improved for other use-cases
Generator genFk = Gensym.create("fk_autogen");
for(Relation relation : relations.values()) {
Collection<ForeignKey> fks = foreignKeys.get(relation.getName());
// If fks exists, we assume that all have been stated
if(!fks.isEmpty()) {
continue;
}
for(Column column : relation.getColumns().values()) {
// Check if a table has the same name as the column
Relation r = relations.get(column.getName());
if(r == null) {
continue;
}
// Without primary key we have no link target
PrimaryKey pk = primaryKeys.get(r.getName());
if(pk == null) {
continue;
}
ColumnsReference source = new ColumnsReference(relation.getName());
source.getColumnNames().add(column.getName());
String fkName = genFk.next();
ForeignKey fk = new ForeignKey(fkName, source, pk.getSource());
foreignKeys.put(relation.getName(), fk);
System.out.println("Auto FK: " + fk);
}
}
//UriSchema uriSchema = new UriSchemaDefault();
String prefix = "http://localhost/am/";
/**
* TODO:
* - N-M Mappings
*/
for(Relation relation : relations.values()) {
ViewDefinition viewDefinition = new ViewDefinition();
viewDefinition.setName("view_" + relation.getName());
//Generator genFkSourceVar = Gensym.create("fk_src");
Generator genFkTargetVar = Gensym.create("fk");
Generator genColumnVar = Gensym.create("o");
if(!relationNames.contains(relation.getName())) {
continue;
}
PrimaryKey primaryKey = primaryKeys.get(relation.getName());
if(primaryKey == null) {
//logger.warn(relation.getName() + ": Neither primary nor foreign keys found. Skipping.");
logger.warn(relation.getName() + ": No primary key found. Skipping.");
continue;
}
Collection<ForeignKey> fks = foreignKeys.get(relation.getName());
// FIXME Replace with safeGet
if(fks == null) {
fks = Collections.emptySet();
}
Var mainVar = Var.alloc("s");
VarDef mainVarDef = columnReferenceToUriTermDef(prefix, primaryKey.getSource());
viewDefinition.addVarDef(mainVar, mainVarDef);
Node classUri = NodeFactory.createURI(prefix + StringUtils.toUpperCamelCase(relation.getName()));
viewDefinition.getConstructPattern().add(new Quad(null, new Triple(mainVar, RDF.type.asNode(), classUri)));
// Create Uris that join according to the FK
for(ForeignKey fk : fks) {
// Product(id) Order(id, product_id) -> uri(?order_id) am:product (?product_id)
/*
Var sourceVar = Var.alloc(genFkSourceVar.next());
TermDef sourceVarDef = columnReferenceToUriTermDef(prefixExpr, fk.getSource());
viewDefinition.addVarDef(sourceVar, mainVarDef);
*/
Var targetVar = Var.alloc(genFkTargetVar.next());
//StringUtils.toUpperCamelCase(fk.getSource().getT)
VarDef targetVarDef = columnReferenceToUriTermDef(prefix, fk.getSource());
viewDefinition.addVarDef(targetVar, targetVarDef);
Node property = NodeFactory.createURI(prefix + StringUtils.toLowerCamelCase(fk.getTarget().getTableName()));
Triple triple = new Triple(mainVar, property, targetVar);
Quad quad = new Quad(Quad.defaultGraphNodeGenerated, triple);
viewDefinition.getConstructPattern().add(quad);
}
List<Column> nonKeyColumns = getNonKeyColumns(relation, primaryKey, fks);
for(Column column : nonKeyColumns) {
Var var = Var.alloc(genColumnVar.next());
SqlDatatype dt = datatypeMap.get(column.getType()); //system.getByName(column.getType());
if(dt == null) {
logger.warn(column.getType() + ": no datatype mapping found");
}
Node datatype = NodeFactory.createURI("http://aksw.org/sparqlify/unknown-datatype");
if(dt != null) {
Node xsd = dt.getXsd();
if(xsd != null) {
datatype = xsd;
}
}
if(datatype.equals(XSD.xstring.asNode())) {
viewDefinition.addVarDef(var, E_RdfTerm.createPlainLiteral(new ExprVar(column.getName())));
} else {
Expr dte = NodeValue.makeString(datatype.toString());
viewDefinition.addVarDef(var, E_RdfTerm.createTypedLiteral(new ExprVar(column.getName()), dte));
}
// FIXME Encode name
Node property = NodeFactory.createURI(prefix + column.getName());
Triple triple = new Triple(mainVar, property, var);
Quad quad = new Quad(Quad.defaultGraphNodeGenerated, triple);
viewDefinition.getConstructPattern().add(quad);
}
//String relationClass = uriSchema.getClassUri(relationName);
//SqlNode relationNode = new SqlTable(relation.getName());
viewDefinition.setRelation(new SqlOpQuery(null, relation.getName()));
System.out.println(viewDefinition.getDefinitionString());
}
/*
for(String relationName : relationNames) {
}*/
}
public AutoMapper() {
}
public static Connection getConnection() throws SQLException {
PGSimpleDataSource dataSource = new PGSimpleDataSource();
dataSource.setDatabaseName("bsbm_default");
dataSource.setUser("postgres");
dataSource.setPassword("postgres");
dataSource.setServerName("localhost");
dataSource.setPortNumber(5432);
Connection conn = dataSource.getConnection();
return conn;
}
public static List<String> getColumnNames(ResultSet rs)
throws SQLException
{
ResultSetMetaData meta = rs.getMetaData();
List<String> result = new ArrayList<String>(meta.getColumnCount());
for(int i = 0; i < meta.getColumnCount(); ++i) {
result.add(meta.getColumnName(i + 1));
}
return result;
}
public static List<Object> getRow(ResultSet rs)
throws SQLException
{
ResultSetMetaData meta = rs.getMetaData();
List<Object> result = new ArrayList<Object>(meta.getColumnCount());
for(int i = 0; i < meta.getColumnCount(); ++i) {
result.add(rs.getObject(i + 1));
}
return result;
}
}