brainbaking/content/wiki/code/db/sqlite.md

249 lines
7.7 KiB
Markdown

+++
title = "sqlite"
draft = false
tags = [
"code",
"db",
"sqlite"
]
date = "2015-07-17"
+++
# SQLite
### DDLs copypasten van Oracle
Bijvoorbeeld de gegenereerde versie (SQL tab) van Oracle SQL Developer overnemen gaat niet zomaar.
Er zijn enkele verschillen:
1. `NOT NULL ENABLE` wegdoen. `NOT NULL` maakt niets uit aangezien het in-memory is om te testen!
2. `USING INDEX` blabla na de `PRIMARY KEY` statements zijn ook allemaal niet nodig (`STORAGE` etc)
3. speciale defaults of syscalls zoals `sys_guid()` bestaan niet.
4. definities van groottes: wegdoen van `BYTE`: `VARCHAR2(26)` dus.
5. opletten met datums: zie onder.
6. Namespace mag ook weg bij `CREATE TABLE "SPACE"."NAME" (`
### Connection strings
Zie http://www.connectionstrings.com/sqlite/
##### File based
```
Data Source######c:<br/>mydb.db;Version3;
```
##### In-memory
```
Data Source######:memory:;Version3;New=True;
```
Conventie `Data Source=` notatie gehanteerd door [http:*www.connectionstrings.com/sqlite/](http:*www.connectionstrings.com/sqlite/)
Andere notatie
```
FullUri######file::memory:?cacheshared
```
Notatie gehanteerd door [https:*www.sqlite.org/inmemorydb.html](https:*www.sqlite.org/inmemorydb.html)
Versie en New hoeft blijkbaar niet (?)
:exclamation: Elke keer als je een connectie opendoet gaat SQLite de inmemory DB state restoren naar default (dus leeg). Als je dus bvb;
1. connectie open
2. `CREATE TABLE;` brol uitvoeren
3. connectie sluiten
4. roep andere method aan
1. connectie open
1. `SELECT * FROM TABLE;` brol uitvoeren
BOEM no such table...
Zie ook [SQLite in memory create table does not work](http://stackoverflow.com/questions/17477246/sqlite-in-memory-database-create-table-does-not-work).
:exclamation: Hou dus 1x een connectie open voor unit testen bij een `[SetUp]` en geef referenties door.
##### Extra opties meegeven via de connection string
Je kan `PRAGMA` parameters zoals [deze](http://www.sqlite.org/pragma.html#pragma_locking_mode) meegeven.
### Integreren met C#
#### DB Connectie
Gebruik http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki (package `System.data.SQLite (x86/x64)`)
Voor `OleDb` C# code, is er voor elke klasse een equivalent in de `SQLite` namespace, bijvoorbeeld:
```csharp
private SQLiteConnection SqLiteDbConnection()
{
return new SQLiteConnection()
{
ConnectionString ###### "Data Sourcemydb.s3db;",
Flags = SQLiteConnectionFlags.LogAll
};
}
public void SetupDb()
{
using (var connection = SqLiteDbConnection())
{
connection.Open();
var transaction = connection.BeginTransaction();
var sqLiteCommand = new SQLiteCommand()
{
Connection = (SQLiteConnection) connection,
CommandType = CommandType.Text,
CommandText = GetSchemaCreateSql()
};
sqLiteCommand.ExecuteNonQuery();
transaction.Commit();
}
}
```
#### Builden op x86/x64 systemen
Afhankelijk van welke package manager je gebruikt (NuGet bvb.) kan een `SQLite.interop.dll` in submapjes `x86` en `x64` geplaatst worden (Copy always als content). Lokaal werkt dit, maar op de build pc (die bijvoorbeeld enkel 32-bit is) niet, omdat de DLL op dezelfde plaats als de executable verwacht wordt. Hier zijn een paar mogelijke oplossingen voor:
1. Gebruik enkel de 32bit versie. (er is zo'n specifieke package voor)
2. "Hint" de DLL loader om de juiste te gebruiken
```csharp
[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet ###### System.Runtime.InteropServices.CharSet.Unicode, SetLastError true)]
[return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
static extern bool SetDllDirectory(string lpPathName);
```
```csharp
[STAThread]
static void Main()
{
int wsize = IntPtr.Size;
string libdir ###### (wsize 4)?"x86":"x64";
string appPath = System.IO.Path.GetDirectoryName(Application.ExecutablePath);
SetDllDirectory(System.IO.Path.Combine(appPath, libdir));
// ...
}
```
Zie ook [http:*stackoverflow.com/questions/13028069/unable-to-load-dll-sqlite-interop-dll](http:*stackoverflow.com/questions/13028069/unable-to-load-dll-sqlite-interop-dll)
#### .NET dates en SQLite dates
Als je een `DATE` kolom hebt, en een SQL zoals gewoon `select * from blah;` uitvoert, kan je de volgende fout krijgen:
```
String was not recognized as a valid DateTime
```
Dit komt doordat SQLite dynamisch getypeerd is en voor hem een date hetzelfde als een char is, gebruik daarvoor de `date()` functie om zelf te parsen! Beetje vervelend in de queries...
Een andere mogelijkheid is `DateTimeFormat=Ticks` in de connection string meegeven.
#### Creating integration tests, using Record objects
Maak objecten die extenden van `DatabaseInsertable`:
```csharp
public abstract class DatabaseInsertable
{
protected abstract string GetTable();
public override string ToString()
{
var fieldDict = FieldDictionary();
var fields = "(" + string.Join(",", fieldDict.Keys) + ")";
var values = "(" + string.Join(",", fieldDict.Values) + ")";
return "insert into " + GetTable() + fields + " values " + values;
}
public void Save()
{
DbConnection.Instance.CreateCommand(ToString()).ExecuteNonQuery();
}
private Dictionary<string, string> FieldDictionary()
{
var dictionary = new Dictionary<string, string>();
foreach (var info in this.GetType().GetFields())
{
if (info.GetValue(this) != null)
{
dictionary.Add(info.Name, "'" + info.GetValue(this).ToString() + "'");
}
}
return dictionary;
}
}
```
Zoals bijvoorbeeld
```csharp
internal class UnitRecord : DatabaseInsertable
{
public string creator;
public string guid;
protected override string GetTable()
{
return "UNIT";
}
}
```
Gebruik:
```csharp
new UnitRecord() { creator ###### "bla"; guid "lala"; }.Save(); // done!
// execute your SUT stuff here
```
`DbConnection` gebruikt dan de SQLite versie om een "unit" record aan te maken in de DB. Merk op dat de properties 100% moeten overeenkomen.
Het enige speciale wat in `IntegrationTestCase` gedefiniëerd is, is de "gewone" `OleDbConnection` vervangen door de SQLite verie:
```csharp
public abstract class IntegrationTestCase
{
protected SqLitedbConnection connection;
[TestInitialize]
public void CleanDb()
{
this.connection = new SqLitedbConnection();
DbConnection.Instance = connection; // instead of OleDbConnection, lazyloaded singleton getter property.
}
}
```
Omdat Ole en SQLite soms andere interfaces hebben moeten we er zelf een anticorruptie laag (de IConnection interface) tussen zetten:
```csharp
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
public interface IdbConnection
{
object QueryProcedure(string procedure, IDictionary<string, object> parameters, string outputParameter);
DbParameter CreateParameter(string field, object value);
DbCommand CreateCommand(string query);
DataSet Query(DbCommand command);
DataSet Query(string query);
}
```
Het verschil is altijd een `SQLiteCommand` in plaats van een `OleDbCommand` aanmaken. Er zijn soms ook subtiele verschillen in het aanmaken en doorgeven van de parameters.