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