From 9276fa101f02a3c2065574e22e703bc89a1f91e2 Mon Sep 17 00:00:00 2001
From: Igor Ralic <igrali@outlook.com>
Date: Sat, 4 Apr 2015 03:05:51 +0200
Subject: [PATCH 1/4] Support for composite primary keys

---
 src/SQLite.Net.Async/SQLiteAsyncConnection.cs |  8 +-
 src/SQLite.Net/Orm.cs                         |  4 +-
 src/SQLite.Net/SQLiteConnection.cs            | 89 +++++++++++++------
 src/SQLite.Net/TableMapping.cs                | 12 ++-
 tests/CreateTableImplicitTest.cs              | 56 ++++++------
 tests/DeleteTest.cs                           | 71 ++++++++++++++-
 tests/InheritanceTest.cs                      |  2 +-
 7 files changed, 172 insertions(+), 70 deletions(-)

diff --git a/src/SQLite.Net.Async/SQLiteAsyncConnection.cs b/src/SQLite.Net.Async/SQLiteAsyncConnection.cs
index 4ca26301e..10bd988c1 100755
--- a/src/SQLite.Net.Async/SQLiteAsyncConnection.cs
+++ b/src/SQLite.Net.Async/SQLiteAsyncConnection.cs
@@ -234,18 +234,18 @@ public Task<CreateTablesResult> CreateTablesAsync([NotNull] params Type[] types)
         }
 
         [PublicAPI]
-        public Task<int> DeleteAsync<T>([NotNull] object pk, CancellationToken cancellationToken = default (CancellationToken))
+        public Task<int> DeleteAsync<T>([NotNull] Dictionary<string,object> pks, CancellationToken cancellationToken = default (CancellationToken))
         {
-            if (pk == null)
+            if (pks == null)
             {
-                throw new ArgumentNullException("pk");
+                throw new ArgumentNullException("pks");
             }
             return Task.Factory.StartNew(() =>
             {
                 var conn = GetConnection();
                 using (conn.Lock())
                 {
-                    return conn.Delete<T>(pk);
+                    return conn.Delete<T>(pks);
                 }
             }, cancellationToken, _taskCreationOptions, _taskScheduler ?? TaskScheduler.Default);
         }
diff --git a/src/SQLite.Net/Orm.cs b/src/SQLite.Net/Orm.cs
index 3d851dd00..7bc55c25a 100755
--- a/src/SQLite.Net/Orm.cs
+++ b/src/SQLite.Net/Orm.cs
@@ -37,11 +37,11 @@ internal static class Orm
         public const string ImplicitIndexSuffix = "Id";
 
         internal static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, IBlobSerializer serializer,
-            IDictionary<Type, string> extraTypeMappings)
+            IDictionary<Type, string> extraTypeMappings, int primaryKeyCount = 0)
         {
             var decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks, serializer, extraTypeMappings) + " ";
 
-            if (p.IsPK)
+            if (p.IsPK && primaryKeyCount == 1)
             {
                 decl += "primary key ";
             }
diff --git a/src/SQLite.Net/SQLiteConnection.cs b/src/SQLite.Net/SQLiteConnection.cs
index 8aad37d1a..c01c01081 100644
--- a/src/SQLite.Net/SQLiteConnection.cs
+++ b/src/SQLite.Net/SQLiteConnection.cs
@@ -367,7 +367,7 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None)
         {
             var map = GetMapping(ty, createFlags);
 
-            var query = "create table if not exists \"" + map.TableName + "\"(\n";
+            var query = new StringBuilder("create table if not exists \"").Append(map.TableName).Append("\"( \n");
 
             var mapColumns = map.Columns;
 
@@ -376,12 +376,25 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None)
                 throw new Exception("Table has no (public) columns");
             }
 
-            var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings));
+            var PKs = mapColumns.Where(c => c.IsPK).ToList();
+
+            var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, PKs.Count));
             var decl = string.Join(",\n", decls.ToArray());
-            query += decl;
-            query += ")";
+            query.Append(decl).Append(",\n");
 
-            var count = Execute(query);
+
+            if (PKs.Count > 1)
+            {
+                query.Append("primary key (").Append(string.Join(",", PKs.Select(pk => pk.Name))).Append(")");
+            }
+            else
+            {
+                query.Remove(query.Length - 2, 2);
+            }
+
+            query.Append(")");
+
+            var count = Execute(query.ToString());
 
             if (count == 0)
             {
@@ -535,6 +548,8 @@ private void MigrateTable(TableMapping map)
 
             var toBeAdded = new List<TableMapping.Column>();
 
+            var PKscount = map.Columns.Where(c => c.IsPK).Count();
+
             foreach (var p in map.Columns)
             {
                 var found = false;
@@ -554,8 +569,13 @@ private void MigrateTable(TableMapping map)
 
             foreach (var p in toBeAdded)
             {
+                if (p.IsPK)
+                {
+                    throw new NotSupportedException("The new column may not have a PRIMARY KEY constraint.");
+                }
+
                 var addCol = "alter table \"" + map.TableName + "\" add column " +
-                             Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings);
+                             Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, PKscount);
                 Execute(addCol);
             }
         }
@@ -1440,9 +1460,16 @@ public int Insert(object obj, string extra, Type objType)
 
             var map = GetMapping(objType);
 
-            if (map.PK != null && map.PK.IsAutoGuid)
+            TableMapping.Column pk = null;
+
+            if (map.PKs != null)
+            {
+                pk = map.PKs.FirstOrDefault(p => p.IsAutoGuid);
+            }
+
+            if (pk != null)
             {
-                var prop = objType.GetRuntimeProperty(map.PK.PropertyName);
+                var prop = objType.GetRuntimeProperty(pk.PropertyName);
                 if (prop != null)
                 {
                     if (prop.GetValue(obj, null).Equals(Guid.Empty))
@@ -1548,25 +1575,23 @@ public int Update(object obj, Type objType)
 
             var map = GetMapping(objType);
 
-            var pk = map.PK;
+            var pks = map.PKs;
 
-            if (pk == null)
+            if (pks == null || pks.Length == 0)
             {
                 throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
             }
 
             var cols = from p in map.Columns
-                where p != pk
+                where !pks.Any(pk => pk == p)
                 select p;
-            var vals = from c in cols
-                select c.GetValue(obj);
-            var ps = new List<object>(vals)
-            {
-                pk.GetValue(obj)
-            };
-            var q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName,
+            var ps = (from c in cols
+                        select c.GetValue(obj)).ToList();
+
+            ps.AddRange(pks.Select(pk=>pk.GetValue(obj)));
+            var q = string.Format("update \"{0}\" set {1} where {2}", map.TableName,
                 string.Join(",", (from c in cols
-                    select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
+                                  select "\"" + c.Name + "\" = ? ").ToArray()), string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
             try
             {
                 rowsAffected = Execute(q, ps.ToArray());
@@ -1631,13 +1656,15 @@ public int UpdateAll(IEnumerable objects, bool runInTransaction = true)
         public int Delete(object objectToDelete)
         {
             var map = GetMapping(objectToDelete.GetType());
-            var pk = map.PK;
-            if (pk == null)
+            var pks = map.PKs;
+            if (pks == null || pks.Length == 0)
             {
                 throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
             }
-            var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
-            return Execute(q, pk.GetValue(objectToDelete));
+            var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
+            var ps = (from pk in pks
+                      select pk.GetValue(objectToDelete)).ToArray();
+            return Execute(q, ps);
         }
 
         /// <summary>
@@ -1653,16 +1680,22 @@ public int Delete(object objectToDelete)
         ///     The type of object.
         /// </typeparam>
         [PublicAPI]
-        public int Delete<T>(object primaryKey)
+        public int Delete<T>(Dictionary<string, object> PKs)
         {
             var map = GetMapping(typeof (T));
-            var pk = map.PK;
-            if (pk == null)
+            var pks = map.PKs;
+            if (pks == null || pks.Length == 0)
             {
                 throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
             }
-            var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
-            return Execute(q, primaryKey);
+            if (PKs.Keys.Except(pks.Select(p => p.Name)).Count() > 0)
+            {
+                throw new NotSupportedException("Cannot delete " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+            }
+            var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", PKs.Keys.Select(pk => "\"" + pk + "\" = ? ")));
+            var ps = (from pk in PKs.Values
+                      select pk).ToArray();
+            return Execute(q, ps);
         }
 
         /// <summary>
diff --git a/src/SQLite.Net/TableMapping.cs b/src/SQLite.Net/TableMapping.cs
index ae275e414..9350e5e72 100644
--- a/src/SQLite.Net/TableMapping.cs
+++ b/src/SQLite.Net/TableMapping.cs
@@ -57,23 +57,21 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
                 }
             }
             Columns = cols.ToArray();
+            PKs = Columns.Where(col => col.IsPK).ToArray();
             foreach (var c in Columns)
             {
                 if (c.IsAutoInc && c.IsPK)
                 {
                     _autoPk = c;
                 }
-                if (c.IsPK)
-                {
-                    PK = c;
-                }
             }
 
             HasAutoIncPK = _autoPk != null;
 
-            if (PK != null)
+            if (PKs.Length > 0)
             {
-                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name);
+                string pksString = string.Join(",", PKs.Select(pk => pk.Name));
+                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, pksString);
             }
             else
             {
@@ -92,7 +90,7 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
         public Column[] Columns { get; private set; }
 
         [PublicAPI]
-        public Column PK { get; private set; }
+        public Column[] PKs { get; private set; }
 
         [PublicAPI]
         public string GetByPrimaryKeySql { get; private set; }
diff --git a/tests/CreateTableImplicitTest.cs b/tests/CreateTableImplicitTest.cs
index 5ef716755..a3c0d839a 100644
--- a/tests/CreateTableImplicitTest.cs
+++ b/tests/CreateTableImplicitTest.cs
@@ -51,10 +51,11 @@ public void ImplicitAutoInc()
 
             TableMapping mapping = db.GetMapping<PkAttribute>();
 
-            Assert.IsNotNull(mapping.PK);
-            Assert.AreEqual("Id", mapping.PK.Name);
-            Assert.IsTrue(mapping.PK.IsPK);
-            Assert.IsTrue(mapping.PK.IsAutoInc);
+            Assert.IsNotNull(mapping.PKs);
+            Assert.IsNotEmpty(mapping.PKs);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.IsTrue(mapping.PKs[0].IsPK);
+            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
         }
 
         [Test]
@@ -66,10 +67,11 @@ public void ImplicitAutoIncAsPassedInTypes()
 
             TableMapping mapping = db.GetMapping<PkAttribute>();
 
-            Assert.IsNotNull(mapping.PK);
-            Assert.AreEqual("Id", mapping.PK.Name);
-            Assert.IsTrue(mapping.PK.IsPK);
-            Assert.IsTrue(mapping.PK.IsAutoInc);
+            Assert.IsNotNull(mapping.PKs);
+            Assert.IsNotEmpty(mapping.PKs);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.IsTrue(mapping.PKs[0].IsPK);
+            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
         }
 
         [Test]
@@ -94,10 +96,11 @@ public void ImplicitPK()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PK);
-            Assert.AreEqual("Id", mapping.PK.Name);
-            Assert.IsTrue(mapping.PK.IsPK);
-            Assert.IsFalse(mapping.PK.IsAutoInc);
+            Assert.IsNotNull(mapping.PKs);
+            Assert.IsNotEmpty(mapping.PKs);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.IsTrue(mapping.PKs[0].IsPK);
+            Assert.IsFalse(mapping.PKs[0].IsAutoInc);
 
             CheckPK(db);
         }
@@ -111,10 +114,11 @@ public void ImplicitPKAutoInc()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PK);
-            Assert.AreEqual("Id", mapping.PK.Name);
-            Assert.IsTrue(mapping.PK.IsPK);
-            Assert.IsTrue(mapping.PK.IsAutoInc);
+            Assert.IsNotNull(mapping.PKs);
+            Assert.IsNotEmpty(mapping.PKs);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.IsTrue(mapping.PKs[0].IsPK);
+            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
         }
 
         [Test]
@@ -126,10 +130,11 @@ public void ImplicitPKAutoIncAsPassedInTypes()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PK);
-            Assert.AreEqual("Id", mapping.PK.Name);
-            Assert.IsTrue(mapping.PK.IsPK);
-            Assert.IsTrue(mapping.PK.IsAutoInc);
+            Assert.IsNotNull(mapping.PKs);
+            Assert.IsNotEmpty(mapping.PKs);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.IsTrue(mapping.PKs[0].IsPK);
+            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
         }
 
         [Test]
@@ -141,10 +146,11 @@ public void ImplicitPkAsPassedInTypes()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PK);
-            Assert.AreEqual("Id", mapping.PK.Name);
-            Assert.IsTrue(mapping.PK.IsPK);
-            Assert.IsFalse(mapping.PK.IsAutoInc);
+            Assert.IsNotNull(mapping.PKs);
+            Assert.IsNotEmpty(mapping.PKs);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.IsTrue(mapping.PKs[0].IsPK);
+            Assert.IsFalse(mapping.PKs[0].IsAutoInc);
         }
 
         [Test]
@@ -156,7 +162,7 @@ public void WithoutImplicitMapping()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNull(mapping.PK);
+            Assert.IsEmpty(mapping.PKs);
 
             TableMapping.Column column = mapping.Columns[2];
             Assert.AreEqual("IndexedId", column.Name);
diff --git a/tests/DeleteTest.cs b/tests/DeleteTest.cs
index 49967e08a..a760cd39d 100644
--- a/tests/DeleteTest.cs
+++ b/tests/DeleteTest.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
 using System.Linq;
 using NUnit.Framework;
 using SQLite.Net.Attributes;
@@ -16,12 +17,27 @@ private class TestTable
             public string Test { get; set; }
         }
 
+        private class TestTableCompositeKey
+        {
+            [PrimaryKey]
+            public int Id { get; set; }
+
+            [PrimaryKey]
+            public int TestIndex { get; set; }
+
+            public int Datum { get; set; }
+            public string Test { get; set; }
+        }
+
         private const int Count = 100;
 
         private SQLiteConnection CreateDb()
         {
             var db = new TestDb();
+
             db.CreateTable<TestTable>();
+            db.CreateTable<TestTableCompositeKey>();
+
             var items =
                 from i in Enumerable.Range(0, Count)
                 select new TestTable
@@ -31,7 +47,20 @@ from i in Enumerable.Range(0, Count)
                 }
                 ;
             db.InsertAll(items);
-            Assert.AreEqual(Count, db.Table<TestTable>().Count());
+
+            var itemsCompositeKey =
+                from i in Enumerable.Range(0, Count)
+                select new TestTableCompositeKey
+                {
+                    Datum = 1000 + i,
+                    Test = "Hello World",
+                    Id = i,
+                    TestIndex = i + 1
+                }
+                ;
+            db.InsertAll(itemsCompositeKey);
+            Assert.AreEqual(Count, db.Table<TestTableCompositeKey>().Count());
+
             return db;
         }
 
@@ -89,7 +118,10 @@ public void DeletePKNone()
         {
             var db = CreateDb();
 
-            var r = db.Delete<TestTable>(348597);
+            var pks = new Dictionary<string, object>();
+            pks.Add("Id", 348597);
+
+            var r = db.Delete<TestTable>(pks);
 
             Assert.AreEqual(0, r);
             Assert.AreEqual(Count, db.Table<TestTable>().Count());
@@ -100,10 +132,43 @@ public void DeletePKOne()
         {
             var db = CreateDb();
 
-            var r = db.Delete<TestTable>(1);
+            var pks = new Dictionary<string, object>();
+            pks.Add("Id", 1);
+
+            var r = db.Delete<TestTable>(pks);
 
             Assert.AreEqual(1, r);
             Assert.AreEqual(Count - 1, db.Table<TestTable>().Count());
         }
+
+        [Test]
+        public void DeletePKNoneComposite()
+        {
+            var db = CreateDb();
+
+            var pks = new Dictionary<string, object>();
+            pks.Add("Id", 348597);
+            pks.Add("TestIndex", 348598);
+
+            var r = db.Delete<TestTableCompositeKey>(pks);
+
+            Assert.AreEqual(0, r);
+            Assert.AreEqual(Count, db.Table<TestTableCompositeKey>().Count());
+        }
+
+        [Test]
+        public void DeletePKOneComposite()
+        {
+            var db = CreateDb();
+
+            var pks = new Dictionary<string, object>();
+            pks.Add("Id", 1);
+            pks.Add("TestIndex", 2);
+
+            var r = db.Delete<TestTableCompositeKey>(pks);
+
+            Assert.AreEqual(1, r);
+            Assert.AreEqual(Count - 1, db.Table<TestTableCompositeKey>().Count());
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/InheritanceTest.cs b/tests/InheritanceTest.cs
index 6b9734084..8455bb1e7 100644
--- a/tests/InheritanceTest.cs
+++ b/tests/InheritanceTest.cs
@@ -28,7 +28,7 @@ public void InheritanceWorks()
             TableMapping mapping = db.GetMapping<Derived>();
 
             Assert.AreEqual(3, mapping.Columns.Length);
-            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.AreEqual("Id", mapping.PKs[0].Name);
         }
     }
 }
\ No newline at end of file

From b090ab2b1ec9589dc5e175e54e7f781039fc5452 Mon Sep 17 00:00:00 2001
From: Igor Ralic <igrali@outlook.com>
Date: Mon, 6 Apr 2015 17:56:54 +0200
Subject: [PATCH 2/4] Update to support composite primary key in Find, Get and
 Delete methods without changing the method signature

---
 src/SQLite.Net.Async/SQLiteAsyncConnection.cs |   8 +-
 src/SQLite.Net/SQLiteConnection.cs            | 107 +++++++++++++++---
 src/SQLite.Net/TableMapping.cs                |  33 ++++--
 tests/DeleteTest.cs                           |  10 +-
 4 files changed, 123 insertions(+), 35 deletions(-)

diff --git a/src/SQLite.Net.Async/SQLiteAsyncConnection.cs b/src/SQLite.Net.Async/SQLiteAsyncConnection.cs
index 10bd988c1..4ca26301e 100755
--- a/src/SQLite.Net.Async/SQLiteAsyncConnection.cs
+++ b/src/SQLite.Net.Async/SQLiteAsyncConnection.cs
@@ -234,18 +234,18 @@ public Task<CreateTablesResult> CreateTablesAsync([NotNull] params Type[] types)
         }
 
         [PublicAPI]
-        public Task<int> DeleteAsync<T>([NotNull] Dictionary<string,object> pks, CancellationToken cancellationToken = default (CancellationToken))
+        public Task<int> DeleteAsync<T>([NotNull] object pk, CancellationToken cancellationToken = default (CancellationToken))
         {
-            if (pks == null)
+            if (pk == null)
             {
-                throw new ArgumentNullException("pks");
+                throw new ArgumentNullException("pk");
             }
             return Task.Factory.StartNew(() =>
             {
                 var conn = GetConnection();
                 using (conn.Lock())
                 {
-                    return conn.Delete<T>(pks);
+                    return conn.Delete<T>(pk);
                 }
             }, cancellationToken, _taskCreationOptions, _taskScheduler ?? TaskScheduler.Default);
         }
diff --git a/src/SQLite.Net/SQLiteConnection.cs b/src/SQLite.Net/SQLiteConnection.cs
index c01c01081..5e0398748 100644
--- a/src/SQLite.Net/SQLiteConnection.cs
+++ b/src/SQLite.Net/SQLiteConnection.cs
@@ -814,7 +814,7 @@ public TableQuery<T> Table<T>() where T : class
         ///     the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
         /// </summary>
         /// <param name="pk">
-        ///     The primary key.
+        ///     The primary key. Needs to be Dictionary<string, object> if table has composite PK.
         /// </param>
         /// <returns>
         ///     The object with the given primary key. Throws a not found exception
@@ -824,7 +824,28 @@ public TableQuery<T> Table<T>() where T : class
         public T Get<T>(object pk) where T : class
         {
             var map = GetMapping(typeof (T));
-            return Query<T>(map.GetByPrimaryKeySql, pk).First();
+            if (map.HasCompositePK)
+            {
+                IDictionary<string, object> PKs = pk as Dictionary<string, object>;
+                if (PKs == null)
+                {
+                    throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
+                }
+                var pks = map.PKs;
+                if (pks == null || pks.Length == 0)
+                {
+                    throw new NotSupportedException("Cannot get from  " + map.TableName + ": it has no PK");
+                }
+                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                {
+                    throw new NotSupportedException("Cannot get from " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                }
+                return Query<T>(map.GetByPrimaryKeySql, PKs.Values.ToArray()).First();
+            }
+            else
+            {
+                return Query<T>(map.GetByPrimaryKeySql, pk).First();
+            }
         }
 
         /// <summary>
@@ -850,7 +871,7 @@ public T Get<T>(Expression<Func<T, bool>> predicate) where T : class
         ///     the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
         /// </summary>
         /// <param name="pk">
-        ///     The primary key.
+        ///     The primary key. Needs to be Dictionary<string, object> if table has composite PK.
         /// </param>
         /// <returns>
         ///     The object with the given primary key or null
@@ -859,8 +880,29 @@ public T Get<T>(Expression<Func<T, bool>> predicate) where T : class
         [PublicAPI]
         public T Find<T>(object pk) where T : class
         {
-            var map = GetMapping(typeof (T));
-            return Query<T>(map.GetByPrimaryKeySql, pk).FirstOrDefault();
+            var map = GetMapping(typeof(T));
+            if (map.HasCompositePK)
+            {
+                IDictionary<string, object> PKs = pk as Dictionary<string, object>;
+                if (PKs == null)
+                {
+                    throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
+                }
+                var pks = map.PKs;
+                if (pks == null || pks.Length == 0)
+                {
+                    throw new NotSupportedException("Cannot find in  " + map.TableName + ": it has no PK");
+                }
+                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                {
+                    throw new NotSupportedException("Cannot find in " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                }
+                return Query<T>(map.GetByPrimaryKeySql, PKs.Values.ToArray()).FirstOrDefault();
+            }
+            else
+            {
+                return Query<T>(map.GetByPrimaryKeySql, pk).FirstOrDefault();
+            }
         }
 
         /// <summary>
@@ -889,7 +931,7 @@ public T FindWithQuery<T>(string query, params object[] args) where T : class
         ///     the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
         /// </summary>
         /// <param name="pk">
-        ///     The primary key.
+        ///     The primary key. Needs to be Dictionary<string, object> if table has composite PK.
         /// </param>
         /// <param name="map">
         ///     The TableMapping used to identify the object type.
@@ -901,7 +943,28 @@ public T FindWithQuery<T>(string query, params object[] args) where T : class
         [PublicAPI]
         public object Find(object pk, TableMapping map)
         {
-            return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault();
+            if (map.HasCompositePK)
+            {
+                IDictionary<string, object> PKs = pk as Dictionary<string, object>;
+                if (PKs == null)
+                {
+                    throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
+                }
+                var pks = map.PKs;
+                if (pks == null || pks.Length == 0)
+                {
+                    throw new NotSupportedException("Cannot find in  " + map.TableName + ": it has no PK");
+                }
+                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                {
+                    throw new NotSupportedException("Cannot find in " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                }
+                return Query(map, map.GetByPrimaryKeySql, PKs.Values.ToArray()).FirstOrDefault();
+            }
+            else
+            {
+                return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault();
+            }
         }
 
         /// <summary>
@@ -1671,7 +1734,7 @@ public int Delete(object objectToDelete)
         ///     Deletes the object with the specified primary key.
         /// </summary>
         /// <param name="primaryKey">
-        ///     The primary key of the object to delete.
+        ///     The primary key of the object to delete. Needs to be Dictionary<string, object> if table has composite PK.
         /// </param>
         /// <returns>
         ///     The number of objects deleted.
@@ -1680,7 +1743,7 @@ public int Delete(object objectToDelete)
         ///     The type of object.
         /// </typeparam>
         [PublicAPI]
-        public int Delete<T>(Dictionary<string, object> PKs)
+        public int Delete<T>(object primaryKey)
         {
             var map = GetMapping(typeof (T));
             var pks = map.PKs;
@@ -1688,14 +1751,28 @@ public int Delete<T>(Dictionary<string, object> PKs)
             {
                 throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
             }
-            if (PKs.Keys.Except(pks.Select(p => p.Name)).Count() > 0)
+
+            if (map.HasCompositePK)
+            {
+                IDictionary<string, object> PKs = primaryKey as Dictionary<string, object>;
+                if (PKs == null)
+                {
+                    throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
+                }
+                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                {
+                    throw new NotSupportedException("Cannot delete " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                }
+                var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", PKs.Keys.Select(pk => "\"" + pk + "\" = ? ")));
+                var ps = (from pk in PKs.Values
+                          select pk).ToArray();
+                return Execute(q, ps);
+            }
+            else
             {
-                throw new NotSupportedException("Cannot delete " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pks.FirstOrDefault().Name);
+                return Execute(q, primaryKey);
             }
-            var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", PKs.Keys.Select(pk => "\"" + pk + "\" = ? ")));
-            var ps = (from pk in PKs.Values
-                      select pk).ToArray();
-            return Execute(q, ps);
         }
 
         /// <summary>
diff --git a/src/SQLite.Net/TableMapping.cs b/src/SQLite.Net/TableMapping.cs
index 9350e5e72..877f61a21 100644
--- a/src/SQLite.Net/TableMapping.cs
+++ b/src/SQLite.Net/TableMapping.cs
@@ -58,20 +58,34 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
             }
             Columns = cols.ToArray();
             PKs = Columns.Where(col => col.IsPK).ToArray();
-            foreach (var c in Columns)
+
+            if (PKs.Length > 1)
+            {
+                HasCompositePK = true;
+            }
+
+            if (!HasCompositePK)
             {
-                if (c.IsAutoInc && c.IsPK)
+                foreach (var c in Columns)
                 {
-                    _autoPk = c;
+                    if (c.IsAutoInc && c.IsPK)
+                    {
+                        _autoPk = c;
+                    }
                 }
-            }
 
-            HasAutoIncPK = _autoPk != null;
+                HasAutoIncPK = _autoPk != null;
+            }
 
-            if (PKs.Length > 0)
+            if (PKs.Length > 1)
+            {
+                string pksString = string.Join(" and ", PKs.Select(pk => "\"" + pk.Name + "\" = ? "));
+                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where {1}", TableName, pksString);
+            }
+            else if (PKs.Length == 1)
             {
-                string pksString = string.Join(",", PKs.Select(pk => pk.Name));
-                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, pksString);
+                string pkString = PKs.FirstOrDefault().Name;
+                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, pkString);
             }
             else
             {
@@ -98,6 +112,9 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
         [PublicAPI]
         public bool HasAutoIncPK { get; private set; }
 
+        [PublicAPI]
+        public bool HasCompositePK { get; private set; }
+
         [PublicAPI]
         public Column[] InsertColumns
         {
diff --git a/tests/DeleteTest.cs b/tests/DeleteTest.cs
index a760cd39d..251c02fcd 100644
--- a/tests/DeleteTest.cs
+++ b/tests/DeleteTest.cs
@@ -118,10 +118,7 @@ public void DeletePKNone()
         {
             var db = CreateDb();
 
-            var pks = new Dictionary<string, object>();
-            pks.Add("Id", 348597);
-
-            var r = db.Delete<TestTable>(pks);
+            var r = db.Delete<TestTable>(348597);
 
             Assert.AreEqual(0, r);
             Assert.AreEqual(Count, db.Table<TestTable>().Count());
@@ -132,10 +129,7 @@ public void DeletePKOne()
         {
             var db = CreateDb();
 
-            var pks = new Dictionary<string, object>();
-            pks.Add("Id", 1);
-
-            var r = db.Delete<TestTable>(pks);
+            var r = db.Delete<TestTable>(1);
 
             Assert.AreEqual(1, r);
             Assert.AreEqual(Count - 1, db.Table<TestTable>().Count());

From 75d3cfb9dfcbd31727990d46a1a802b91b96bceb Mon Sep 17 00:00:00 2001
From: Igor Ralic <igrali@outlook.com>
Date: Sat, 11 Apr 2015 14:56:07 +0200
Subject: [PATCH 3/4] Keep both PK and composite PK as a part of TableMapping
 public API.

---
 src/SQLite.Net/Orm.cs              |   4 +-
 src/SQLite.Net/SQLiteConnection.cs | 141 ++++++++++++++++++-----------
 src/SQLite.Net/TableMapping.cs     |  22 +++++
 tests/CreateTableImplicitTest.cs   |  63 ++++++-------
 tests/InheritanceTest.cs           |   2 +-
 5 files changed, 146 insertions(+), 86 deletions(-)

diff --git a/src/SQLite.Net/Orm.cs b/src/SQLite.Net/Orm.cs
index 7bc55c25a..bdea44225 100755
--- a/src/SQLite.Net/Orm.cs
+++ b/src/SQLite.Net/Orm.cs
@@ -37,11 +37,11 @@ internal static class Orm
         public const string ImplicitIndexSuffix = "Id";
 
         internal static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, IBlobSerializer serializer,
-            IDictionary<Type, string> extraTypeMappings, int primaryKeyCount = 0)
+            IDictionary<Type, string> extraTypeMappings, bool hasCompositePK = false)
         {
             var decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks, serializer, extraTypeMappings) + " ";
 
-            if (p.IsPK && primaryKeyCount == 1)
+            if (p.IsPK && !hasCompositePK)
             {
                 decl += "primary key ";
             }
diff --git a/src/SQLite.Net/SQLiteConnection.cs b/src/SQLite.Net/SQLiteConnection.cs
index 5e0398748..86a64cfcb 100644
--- a/src/SQLite.Net/SQLiteConnection.cs
+++ b/src/SQLite.Net/SQLiteConnection.cs
@@ -376,24 +376,23 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None)
                 throw new Exception("Table has no (public) columns");
             }
 
-            var PKs = mapColumns.Where(c => c.IsPK).ToList();
-
-            var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, PKs.Count));
-            var decl = string.Join(",\n", decls.ToArray());
-            query.Append(decl).Append(",\n");
-
-
-            if (PKs.Count > 1)
+            if (map.HasCompositePK)
             {
+                var PKs = mapColumns.Where(c => c.IsPK).ToList();
+
+                var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, map.HasCompositePK));
+                var decl = string.Join(",\n", decls.ToArray());
+                query.Append(decl).Append(",\n");
                 query.Append("primary key (").Append(string.Join(",", PKs.Select(pk => pk.Name))).Append(")");
+                query.Append(")");
             }
             else
             {
-                query.Remove(query.Length - 2, 2);
+                var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings));
+                var decl = string.Join(",\n", decls.ToArray());
+                query.Append(decl).Append(")");
             }
 
-            query.Append(")");
-
             var count = Execute(query.ToString());
 
             if (count == 0)
@@ -575,7 +574,7 @@ private void MigrateTable(TableMapping map)
                 }
 
                 var addCol = "alter table \"" + map.TableName + "\" add column " +
-                             Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, PKscount);
+                             Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, map.HasCompositePK);
                 Execute(addCol);
             }
         }
@@ -832,10 +831,6 @@ public T Get<T>(object pk) where T : class
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
                 var pks = map.PKs;
-                if (pks == null || pks.Length == 0)
-                {
-                    throw new NotSupportedException("Cannot get from  " + map.TableName + ": it has no PK");
-                }
                 if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
                 {
                     throw new NotSupportedException("Cannot get from " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
@@ -889,10 +884,6 @@ public T Find<T>(object pk) where T : class
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
                 var pks = map.PKs;
-                if (pks == null || pks.Length == 0)
-                {
-                    throw new NotSupportedException("Cannot find in  " + map.TableName + ": it has no PK");
-                }
                 if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
                 {
                     throw new NotSupportedException("Cannot find in " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
@@ -951,10 +942,6 @@ public object Find(object pk, TableMapping map)
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
                 var pks = map.PKs;
-                if (pks == null || pks.Length == 0)
-                {
-                    throw new NotSupportedException("Cannot find in  " + map.TableName + ": it has no PK");
-                }
                 if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
                 {
                     throw new NotSupportedException("Cannot find in " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
@@ -1522,13 +1509,19 @@ public int Insert(object obj, string extra, Type objType)
             }
 
             var map = GetMapping(objType);
-
             TableMapping.Column pk = null;
 
-            if (map.PKs != null)
+            if (map.HasCompositePK)
             {
                 pk = map.PKs.FirstOrDefault(p => p.IsAutoGuid);
             }
+            else
+            {
+                if (map.PK != null && map.PK.IsAutoGuid)
+                {
+                    pk = map.PK;
+                }
+            }
 
             if (pk != null)
             {
@@ -1637,27 +1630,57 @@ public int Update(object obj, Type objType)
             }
 
             var map = GetMapping(objType);
+            string q = null;
+            object[] ps = null;
 
-            var pks = map.PKs;
-
-            if (pks == null || pks.Length == 0)
+            if (map.HasCompositePK)
             {
-                throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
+                var pks = map.PKs;
+                var cols = from p in map.Columns
+                           where !pks.Any(pk => pk == p)
+                           select p;
+
+                var pslist = (from c in cols
+                              select c.GetValue(obj)).ToList();
+
+                pslist.AddRange(pks.Select(pk => pk.GetValue(obj)));
+
+                q = string.Format("update \"{0}\" set {1} where {2}", map.TableName,
+                        string.Join(",", (from c in cols
+                                          select "\"" + c.Name + "\" = ? ").ToArray()), string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
+
+                ps = pslist.ToArray();
             }
+            else
+            {
+                var pk = map.PK;
 
-            var cols = from p in map.Columns
-                where !pks.Any(pk => pk == p)
-                select p;
-            var ps = (from c in cols
-                        select c.GetValue(obj)).ToList();
+                if (pk == null)
+                {
+                    throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK");
+                }
+
+                var cols = from p in map.Columns
+                           where p != pk
+                           select p;
+
+                var vals = from c in cols
+                           select c.GetValue(obj);
+                var pslist = new List<object>(vals)
+                {
+                    pk.GetValue(obj)
+                };
+
+                q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName,
+                        string.Join(",", (from c in cols
+                        select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name);
+
+                ps = pslist.ToArray();
+            }
 
-            ps.AddRange(pks.Select(pk=>pk.GetValue(obj)));
-            var q = string.Format("update \"{0}\" set {1} where {2}", map.TableName,
-                string.Join(",", (from c in cols
-                                  select "\"" + c.Name + "\" = ? ").ToArray()), string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
             try
             {
-                rowsAffected = Execute(q, ps.ToArray());
+                rowsAffected = Execute(q, ps);
             }
             catch (SQLiteException ex)
             {
@@ -1719,14 +1742,27 @@ public int UpdateAll(IEnumerable objects, bool runInTransaction = true)
         public int Delete(object objectToDelete)
         {
             var map = GetMapping(objectToDelete.GetType());
-            var pks = map.PKs;
-            if (pks == null || pks.Length == 0)
+            string q = null;
+            object[] ps = null;
+
+            if (map.HasCompositePK)
             {
-                throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
-            }
-            var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
-            var ps = (from pk in pks
+                var pks = map.PKs;
+                q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
+                ps = (from pk in pks
                       select pk.GetValue(objectToDelete)).ToArray();
+            }
+            else
+            {
+                var pk = map.PK;
+                if (pk == null)
+                {
+                    throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
+                }
+                q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
+                ps = new object[] { pk.GetValue(objectToDelete) };
+            }
+
             return Execute(q, ps);
         }
 
@@ -1746,14 +1782,10 @@ public int Delete(object objectToDelete)
         public int Delete<T>(object primaryKey)
         {
             var map = GetMapping(typeof (T));
-            var pks = map.PKs;
-            if (pks == null || pks.Length == 0)
-            {
-                throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
-            }
 
             if (map.HasCompositePK)
             {
+                var pks = map.PKs;
                 IDictionary<string, object> PKs = primaryKey as Dictionary<string, object>;
                 if (PKs == null)
                 {
@@ -1770,7 +1802,12 @@ public int Delete<T>(object primaryKey)
             }
             else
             {
-                var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pks.FirstOrDefault().Name);
+                var pk = map.PK;
+                if (pk == null)
+                {
+                    throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK");
+                }
+                var q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name);
                 return Execute(q, primaryKey);
             }
         }
diff --git a/src/SQLite.Net/TableMapping.cs b/src/SQLite.Net/TableMapping.cs
index 877f61a21..fadbadb13 100644
--- a/src/SQLite.Net/TableMapping.cs
+++ b/src/SQLite.Net/TableMapping.cs
@@ -34,6 +34,7 @@ public class TableMapping
     {
         private readonly Column _autoPk;
         private Column[] _insertColumns;
+        private Column _pk;
 
         [PublicAPI]
         public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags createFlags = CreateFlags.None)
@@ -72,6 +73,11 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
                     {
                         _autoPk = c;
                     }
+
+                    if (c.IsPK)
+                    {
+                        _pk = c;
+                    }
                 }
 
                 HasAutoIncPK = _autoPk != null;
@@ -103,6 +109,22 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
         [PublicAPI]
         public Column[] Columns { get; private set; }
 
+        [PublicAPI]
+        public Column PK
+        {
+            get
+            {
+                if (HasCompositePK)
+                {
+                    throw new NotSupportedException("Table has a composite primary key. Use PKs property instead.");
+                }
+                else
+                {
+                    return _pk;
+                }
+            }
+        }
+
         [PublicAPI]
         public Column[] PKs { get; private set; }
 
diff --git a/tests/CreateTableImplicitTest.cs b/tests/CreateTableImplicitTest.cs
index a3c0d839a..13079a3d3 100644
--- a/tests/CreateTableImplicitTest.cs
+++ b/tests/CreateTableImplicitTest.cs
@@ -51,11 +51,11 @@ public void ImplicitAutoInc()
 
             TableMapping mapping = db.GetMapping<PkAttribute>();
 
-            Assert.IsNotNull(mapping.PKs);
-            Assert.IsNotEmpty(mapping.PKs);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
-            Assert.IsTrue(mapping.PKs[0].IsPK);
-            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNotNull(mapping.PK);
+            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.IsTrue(mapping.PK.IsPK);
+            Assert.IsTrue(mapping.PK.IsAutoInc);
         }
 
         [Test]
@@ -67,11 +67,11 @@ public void ImplicitAutoIncAsPassedInTypes()
 
             TableMapping mapping = db.GetMapping<PkAttribute>();
 
-            Assert.IsNotNull(mapping.PKs);
-            Assert.IsNotEmpty(mapping.PKs);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
-            Assert.IsTrue(mapping.PKs[0].IsPK);
-            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNotNull(mapping.PK);
+            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.IsTrue(mapping.PK.IsPK);
+            Assert.IsTrue(mapping.PK.IsAutoInc);
         }
 
         [Test]
@@ -96,11 +96,11 @@ public void ImplicitPK()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PKs);
-            Assert.IsNotEmpty(mapping.PKs);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
-            Assert.IsTrue(mapping.PKs[0].IsPK);
-            Assert.IsFalse(mapping.PKs[0].IsAutoInc);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNotNull(mapping.PK);
+            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.IsTrue(mapping.PK.IsPK);
+            Assert.IsFalse(mapping.PK.IsAutoInc);
 
             CheckPK(db);
         }
@@ -114,11 +114,11 @@ public void ImplicitPKAutoInc()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PKs);
-            Assert.IsNotEmpty(mapping.PKs);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
-            Assert.IsTrue(mapping.PKs[0].IsPK);
-            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNotNull(mapping.PK);
+            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.IsTrue(mapping.PK.IsPK);
+            Assert.IsTrue(mapping.PK.IsAutoInc);
         }
 
         [Test]
@@ -130,11 +130,11 @@ public void ImplicitPKAutoIncAsPassedInTypes()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PKs);
-            Assert.IsNotEmpty(mapping.PKs);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
-            Assert.IsTrue(mapping.PKs[0].IsPK);
-            Assert.IsTrue(mapping.PKs[0].IsAutoInc);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNotNull(mapping.PK);
+            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.IsTrue(mapping.PK.IsPK);
+            Assert.IsTrue(mapping.PK.IsAutoInc);
         }
 
         [Test]
@@ -146,11 +146,11 @@ public void ImplicitPkAsPassedInTypes()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsNotNull(mapping.PKs);
-            Assert.IsNotEmpty(mapping.PKs);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
-            Assert.IsTrue(mapping.PKs[0].IsPK);
-            Assert.IsFalse(mapping.PKs[0].IsAutoInc);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNotNull(mapping.PK);
+            Assert.AreEqual("Id", mapping.PK.Name);
+            Assert.IsTrue(mapping.PK.IsPK);
+            Assert.IsFalse(mapping.PK.IsAutoInc);
         }
 
         [Test]
@@ -162,7 +162,8 @@ public void WithoutImplicitMapping()
 
             TableMapping mapping = db.GetMapping<NoAttributes>();
 
-            Assert.IsEmpty(mapping.PKs);
+            Assert.IsFalse(mapping.HasCompositePK);
+            Assert.IsNull(mapping.PK);
 
             TableMapping.Column column = mapping.Columns[2];
             Assert.AreEqual("IndexedId", column.Name);
diff --git a/tests/InheritanceTest.cs b/tests/InheritanceTest.cs
index 8455bb1e7..6b9734084 100644
--- a/tests/InheritanceTest.cs
+++ b/tests/InheritanceTest.cs
@@ -28,7 +28,7 @@ public void InheritanceWorks()
             TableMapping mapping = db.GetMapping<Derived>();
 
             Assert.AreEqual(3, mapping.Columns.Length);
-            Assert.AreEqual("Id", mapping.PKs[0].Name);
+            Assert.AreEqual("Id", mapping.PK.Name);
         }
     }
 }
\ No newline at end of file

From b7a55714f4a8d30f8c54c0d86d9c9a307d59988f Mon Sep 17 00:00:00 2001
From: Igor Ralic <igrali@outlook.com>
Date: Sat, 11 Apr 2015 15:19:15 +0200
Subject: [PATCH 4/4] Cleanup and renaming PKs to CompositePK

---
 src/SQLite.Net/SQLiteConnection.cs | 72 +++++++++++++++---------------
 src/SQLite.Net/TableMapping.cs     | 18 ++++----
 tests/DeleteTest.cs                | 16 +++----
 3 files changed, 52 insertions(+), 54 deletions(-)

diff --git a/src/SQLite.Net/SQLiteConnection.cs b/src/SQLite.Net/SQLiteConnection.cs
index 86a64cfcb..952db75b3 100644
--- a/src/SQLite.Net/SQLiteConnection.cs
+++ b/src/SQLite.Net/SQLiteConnection.cs
@@ -378,12 +378,12 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None)
 
             if (map.HasCompositePK)
             {
-                var PKs = mapColumns.Where(c => c.IsPK).ToList();
+                var compositePK = mapColumns.Where(c => c.IsPK).ToList();
 
                 var decls = mapColumns.Select(p => Orm.SqlDecl(p, StoreDateTimeAsTicks, Serializer, ExtraTypeMappings, map.HasCompositePK));
                 var decl = string.Join(",\n", decls.ToArray());
                 query.Append(decl).Append(",\n");
-                query.Append("primary key (").Append(string.Join(",", PKs.Select(pk => pk.Name))).Append(")");
+                query.Append("primary key (").Append(string.Join(",", compositePK.Select(pk => pk.Name))).Append(")");
                 query.Append(")");
             }
             else
@@ -547,8 +547,6 @@ private void MigrateTable(TableMapping map)
 
             var toBeAdded = new List<TableMapping.Column>();
 
-            var PKscount = map.Columns.Where(c => c.IsPK).Count();
-
             foreach (var p in map.Columns)
             {
                 var found = false;
@@ -825,17 +823,17 @@ public T Get<T>(object pk) where T : class
             var map = GetMapping(typeof (T));
             if (map.HasCompositePK)
             {
-                IDictionary<string, object> PKs = pk as Dictionary<string, object>;
-                if (PKs == null)
+                IDictionary<string, object> compositePK = pk as Dictionary<string, object>;
+                if (compositePK == null)
                 {
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
-                var pks = map.PKs;
-                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                var cpk = map.CompositePK;
+                if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
                 {
-                    throw new NotSupportedException("Cannot get from " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                    throw new NotSupportedException("Cannot get from " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
                 }
-                return Query<T>(map.GetByPrimaryKeySql, PKs.Values.ToArray()).First();
+                return Query<T>(map.GetByPrimaryKeySql, compositePK.Values.ToArray()).First();
             }
             else
             {
@@ -878,17 +876,17 @@ public T Find<T>(object pk) where T : class
             var map = GetMapping(typeof(T));
             if (map.HasCompositePK)
             {
-                IDictionary<string, object> PKs = pk as Dictionary<string, object>;
-                if (PKs == null)
+                IDictionary<string, object> compositePK = pk as Dictionary<string, object>;
+                if (compositePK == null)
                 {
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
-                var pks = map.PKs;
-                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                var cpk = map.CompositePK;
+                if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
                 {
-                    throw new NotSupportedException("Cannot find in " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                    throw new NotSupportedException("Cannot find in " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
                 }
-                return Query<T>(map.GetByPrimaryKeySql, PKs.Values.ToArray()).FirstOrDefault();
+                return Query<T>(map.GetByPrimaryKeySql, compositePK.Values.ToArray()).FirstOrDefault();
             }
             else
             {
@@ -936,17 +934,17 @@ public object Find(object pk, TableMapping map)
         {
             if (map.HasCompositePK)
             {
-                IDictionary<string, object> PKs = pk as Dictionary<string, object>;
-                if (PKs == null)
+                IDictionary<string, object> compositePK = pk as Dictionary<string, object>;
+                if (compositePK == null)
                 {
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
-                var pks = map.PKs;
-                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                var cpk = map.CompositePK;
+                if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
                 {
-                    throw new NotSupportedException("Cannot find in " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                    throw new NotSupportedException("Cannot find in " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
                 }
-                return Query(map, map.GetByPrimaryKeySql, PKs.Values.ToArray()).FirstOrDefault();
+                return Query(map, map.GetByPrimaryKeySql, compositePK.Values.ToArray()).FirstOrDefault();
             }
             else
             {
@@ -1513,7 +1511,7 @@ public int Insert(object obj, string extra, Type objType)
 
             if (map.HasCompositePK)
             {
-                pk = map.PKs.FirstOrDefault(p => p.IsAutoGuid);
+                pk = map.CompositePK.FirstOrDefault(p => p.IsAutoGuid);
             }
             else
             {
@@ -1635,19 +1633,19 @@ public int Update(object obj, Type objType)
 
             if (map.HasCompositePK)
             {
-                var pks = map.PKs;
+                var compositePK = map.CompositePK;
                 var cols = from p in map.Columns
-                           where !pks.Any(pk => pk == p)
+                           where !compositePK.Any(pk => pk == p)
                            select p;
 
                 var pslist = (from c in cols
                               select c.GetValue(obj)).ToList();
 
-                pslist.AddRange(pks.Select(pk => pk.GetValue(obj)));
+                pslist.AddRange(compositePK.Select(pk => pk.GetValue(obj)));
 
                 q = string.Format("update \"{0}\" set {1} where {2}", map.TableName,
                         string.Join(",", (from c in cols
-                                          select "\"" + c.Name + "\" = ? ").ToArray()), string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
+                                          select "\"" + c.Name + "\" = ? ").ToArray()), string.Join(" and ", compositePK.Select(pk => "\"" + pk.Name + "\" = ? ")));
 
                 ps = pslist.ToArray();
             }
@@ -1747,9 +1745,9 @@ public int Delete(object objectToDelete)
 
             if (map.HasCompositePK)
             {
-                var pks = map.PKs;
-                q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", pks.Select(pk => "\"" + pk.Name + "\" = ? ")));
-                ps = (from pk in pks
+                var compositePK = map.CompositePK;
+                q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", compositePK.Select(pk => "\"" + pk.Name + "\" = ? ")));
+                ps = (from pk in compositePK
                       select pk.GetValue(objectToDelete)).ToArray();
             }
             else
@@ -1785,18 +1783,18 @@ public int Delete<T>(object primaryKey)
 
             if (map.HasCompositePK)
             {
-                var pks = map.PKs;
-                IDictionary<string, object> PKs = primaryKey as Dictionary<string, object>;
-                if (PKs == null)
+                var cpk = map.CompositePK;
+                IDictionary<string, object> compositePK = primaryKey as Dictionary<string, object>;
+                if (compositePK == null)
                 {
                     throw new NotSupportedException(map.TableName + " table has a composite primary key. Make sure primary key is passed in as Dictionary<string, object>.");
                 }
-                if (PKs.Keys.Intersect(pks.Select(p => p.Name)).Count() < pks.Length)
+                if (compositePK.Keys.Intersect(cpk.Select(p => p.Name)).Count() < cpk.Length)
                 {
-                    throw new NotSupportedException("Cannot delete " + map.TableName + ": PKs mismatch. Make sure PK names are valid.");
+                    throw new NotSupportedException("Cannot delete " + map.TableName + ": CompositePK mismatch. Make sure PK names are valid.");
                 }
-                var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", PKs.Keys.Select(pk => "\"" + pk + "\" = ? ")));
-                var ps = (from pk in PKs.Values
+                var q = string.Format("delete from \"{0}\" where {1}", map.TableName, string.Join(" and ", compositePK.Keys.Select(pk => "\"" + pk + "\" = ? ")));
+                var ps = (from pk in compositePK.Values
                           select pk).ToArray();
                 return Execute(q, ps);
             }
diff --git a/src/SQLite.Net/TableMapping.cs b/src/SQLite.Net/TableMapping.cs
index 900e04b04..ba70b8e6e 100644
--- a/src/SQLite.Net/TableMapping.cs
+++ b/src/SQLite.Net/TableMapping.cs
@@ -58,9 +58,9 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
                 }
             }
             Columns = cols.ToArray();
-            PKs = Columns.Where(col => col.IsPK).ToArray();
+            CompositePK = Columns.Where(col => col.IsPK).ToArray();
 
-            if (PKs.Length > 1)
+            if (CompositePK.Length > 1)
             {
                 HasCompositePK = true;
             }
@@ -83,14 +83,14 @@ public TableMapping(Type type, IEnumerable<PropertyInfo> properties, CreateFlags
                 HasAutoIncPK = _autoPk != null;
             }
 
-            if (PKs.Length > 1)
+            if (CompositePK.Length > 1)
             {
-                string pksString = string.Join(" and ", PKs.Select(pk => "\"" + pk.Name + "\" = ? "));
-                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where {1}", TableName, pksString);
+                string compositePKString = string.Join(" and ", CompositePK.Select(pk => "\"" + pk.Name + "\" = ? "));
+                GetByPrimaryKeySql = string.Format("select * from \"{0}\" where {1}", TableName, compositePKString);
             }
-            else if (PKs.Length == 1)
+            else if (PK != null)
             {
-                string pkString = PKs.FirstOrDefault().Name;
+                string pkString = PK.Name;
                 GetByPrimaryKeySql = string.Format("select * from \"{0}\" where \"{1}\" = ?", TableName, pkString);
             }
             else
@@ -116,7 +116,7 @@ public Column PK
             {
                 if (HasCompositePK)
                 {
-                    throw new NotSupportedException("Table has a composite primary key. Use PKs property instead.");
+                    throw new NotSupportedException("Table has a composite primary key. Use CompositePK property instead.");
                 }
                 else
                 {
@@ -126,7 +126,7 @@ public Column PK
         }
 
         [PublicAPI]
-        public Column[] PKs { get; private set; }
+        public Column[] CompositePK { get; private set; }
 
         [PublicAPI]
         public string GetByPrimaryKeySql { get; private set; }
diff --git a/tests/DeleteTest.cs b/tests/DeleteTest.cs
index 11d48a094..6de95a320 100644
--- a/tests/DeleteTest.cs
+++ b/tests/DeleteTest.cs
@@ -164,11 +164,11 @@ public void DeletePKNoneComposite()
         {
             var db = CreateDb();
 
-            var pks = new Dictionary<string, object>();
-            pks.Add("Id", 348597);
-            pks.Add("TestIndex", 348598);
+            var compositePK = new Dictionary<string, object>();
+            compositePK.Add("Id", 348597);
+            compositePK.Add("TestIndex", 348598);
 
-            var r = db.Delete<TestTableCompositeKey>(pks);
+            var r = db.Delete<TestTableCompositeKey>(compositePK);
 
             Assert.AreEqual(0, r);
             Assert.AreEqual(Count, db.Table<TestTableCompositeKey>().Count());
@@ -179,11 +179,11 @@ public void DeletePKOneComposite()
         {
             var db = CreateDb();
 
-            var pks = new Dictionary<string, object>();
-            pks.Add("Id", 1);
-            pks.Add("TestIndex", 2);
+            var compositePK = new Dictionary<string, object>();
+            compositePK.Add("Id", 1);
+            compositePK.Add("TestIndex", 2);
 
-            var r = db.Delete<TestTableCompositeKey>(pks);
+            var r = db.Delete<TestTableCompositeKey>(compositePK);
 
             Assert.AreEqual(1, r);
             Assert.AreEqual(Count - 1, db.Table<TestTableCompositeKey>().Count());