001package org.hl7.fhir.r5.utils.sql;
002
003import java.sql.Connection;
004import java.sql.PreparedStatement;
005import java.sql.SQLType;
006import java.util.List;
007
008import org.hl7.fhir.exceptions.FHIRException;
009import org.hl7.fhir.r5.model.Base;
010import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
011
012public class StorageSqlite3 implements Storage {
013  
014  public static class SQLiteStore extends Store {
015    private PreparedStatement p;
016
017    protected SQLiteStore(String name, PreparedStatement p) {
018      super(name);
019      this.p = p;
020    }
021
022    public PreparedStatement getP() {
023      return p;
024    }
025    
026  }
027  
028  private Connection conn;
029  private int nextKey = 0;
030  
031  public StorageSqlite3(Connection conn) {
032    super();
033    this.conn = conn;
034  }
035
036  @Override
037  public Store createStore(String name, List<Column> columns) {
038    try {
039      CommaSeparatedStringBuilder fields = new CommaSeparatedStringBuilder(", ");
040      CommaSeparatedStringBuilder values = new CommaSeparatedStringBuilder(", ");
041      StringBuilder b = new StringBuilder();
042      b.append("Create Table "+name+" ( ");
043      b.append("ViewRowKey integer NOT NULL");
044      for (Column column : columns) {
045        b.append(", "+column.getName()+" "+sqliteType(column.getKind())+" NULL"); // index columns are always nullable
046        fields.append(column.getName());
047        values.append("?");
048      }
049      b.append(", PRIMARY KEY (ViewRowKey))\r\n");
050      conn.createStatement().execute(b.toString());
051
052      String isql = "Insert into "+name+" (ViewRowKey, "+fields.toString()+") values (?, "+values.toString()+")";
053      PreparedStatement psql = conn.prepareStatement(isql);
054      return new SQLiteStore(name, psql);
055    } catch (Exception e) {
056      throw new FHIRException(e);
057    }
058  }
059
060  private String sqliteType(ColumnKind type) {
061    switch (type) {
062    case DateTime: return "Text";
063    case Decimal: return "Real";
064    case Integer: return "Integer";
065    case String: return "Text";
066    case Time: return "Text";
067    case Binary: return "Text";
068    case Boolean: return "Integer";
069    case Complex: throw new FHIRException("SQLite runner does not handle complexes");
070    }
071    return null;
072  }
073
074  @Override
075  public void addRow(Store store, List<Cell> cells) {
076    try {
077      SQLiteStore sqls = (SQLiteStore) store;
078      PreparedStatement p = sqls.getP();
079      p.setInt(1, ++nextKey);
080      for (int i = 0; i < cells.size(); i++) {
081        Cell c = cells.get(i);
082        switch (c.getColumn().getKind()) {
083        case Null: 
084          p.setNull(i+2, java.sql.Types.NVARCHAR);
085        case Binary:
086          p.setBytes(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueBinary());
087          break;
088        case Boolean:
089          p.setBoolean(i+2, c.getValues().size() == 0 ? false : c.getValues().get(0).getValueBoolean().booleanValue());
090          break;
091        case DateTime:
092          p.setDate(i+2, c.getValues().size() == 0 ? null : new java.sql.Date(c.getValues().get(0).getValueDate().getTime()));
093          break;
094        case Decimal:
095          p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString());
096          break;
097        case Integer:
098          p.setInt(i+2, c.getValues().size() == 0 ? 0 : c.getValues().get(0).getValueInt().intValue());
099          break;
100        case String:
101          p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString());
102          break;
103        case Time:
104          p.setString(i+2, c.getValues().size() == 0 ? null : c.getValues().get(0).getValueString());
105          break;    
106        case Complex: throw new FHIRException("SQLite runner does not handle complexes");
107        }
108      }
109      p.execute();
110    } catch (Exception e) {
111      throw new FHIRException(e);
112    }
113    
114  }
115
116  @Override
117  public void finish(Store store) {
118    // nothing
119  }
120
121  @Override
122  public boolean supportsArrays() {
123    return false;
124  }
125
126  @Override
127  public boolean supportsComplexTypes() {
128    return false;
129  }
130
131  @Override
132  public boolean needsName() {
133    return true;
134  }
135
136  @Override
137  public String getKeyForSourceResource(Base res) {
138    throw new Error("Key management for resources isn't decided yet");
139  }
140
141  @Override
142  public String getKeyForTargetResource(Base res) {
143    throw new Error("Key management for resources isn't decided yet");
144  }
145}