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