System.Data.SQLite
Check-in [c1baff799b]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Refactor INSERT/UPDATE handling (in the LINQ assembly) so it can handle composite and non-integer primary keys. Fix for [41aea496e0].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | preRelease
Files: files | file ages | folders
SHA1:c1baff799b6affc3d6087099c6930e76c8e20563
User & Date: mistachkin 2015-08-19 04:31:11
References
2015-08-19
04:32 Ticket [41aea496e0] EF6 fails to insert row with GUID as PK column status still Closed with 3 other changes artifact: c00b8f659a user: mistachkin
Context
2015-08-19
04:52
Fix the exclusion file syntax for use by the command line 'zip' tool. check-in: 259a546366 user: mistachkin tags: preRelease
04:31
Refactor INSERT/UPDATE handling (in the LINQ assembly) so it can handle composite and non-integer primary keys. Fix for [41aea496e0]. check-in: c1baff799b user: mistachkin tags: preRelease
04:23
Corrections to the test cases for tickets [41aea496e0] and [9d353b0bd8]. Closed-Leaf check-in: e8e6cb1409 user: mistachkin tags: tkt-41aea496e0
2015-08-18
23:12
Fix release process issues impacting the master release archive manifest and the built packages. check-in: d7fe8b7801 user: mistachkin tags: preRelease
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Doc/Extra/Provider/version.html.

49
50
51
52
53
54
55

56
57
58
59
60
61
62
      <li>Add full support for Visual Studio 2015 and the .NET Framework 4.6.</li>
      <li>Add support for creating custom SQL functions using delegates.</li>
      <li>Implement the Substring method for LINQ using the &quot;substr&quot; core SQL function.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Prevent encrypted connections from being used with the connection pool. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/89d3a159f1">[89d3a159f1]</a>.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Honor the second argument to Math.Round when using LINQ.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Honor the pre-existing flags for connections during the Open method. Fix for <a href="https://system.data.sqlite.org/index.html/info/964063da16">[964063da16]</a>.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for <a href="https://system.data.sqlite.org/index.html/info/9d353b0bd8">[9d353b0bd8]</a>.</li>

      <li>Change the base type for the SQLiteConnectionFlags enumeration to long integer.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Add extended return codes to the SQLiteErrorCode enumeration. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/71bedaca19">[71bedaca19]</a>.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Improve exception handling in all native callbacks implemented in the SQLiteConnection class.</li>
      <li>Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.</li>
      <li>Add &quot;Recursive Triggers&quot; connection string property to enable or disable the recursive trigger capability. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/3a82ee635b">[3a82ee635b]</a>.</li>
      <li>Add NoDefaultFlags connection string property to prevent the default connection flags from being used. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/964063da16">[964063da16]</a>.</li>
      <li>Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.</li>







>







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
      <li>Add full support for Visual Studio 2015 and the .NET Framework 4.6.</li>
      <li>Add support for creating custom SQL functions using delegates.</li>
      <li>Implement the Substring method for LINQ using the &quot;substr&quot; core SQL function.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Prevent encrypted connections from being used with the connection pool. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/89d3a159f1">[89d3a159f1]</a>.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Honor the second argument to Math.Round when using LINQ.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Honor the pre-existing flags for connections during the Open method. Fix for <a href="https://system.data.sqlite.org/index.html/info/964063da16">[964063da16]</a>.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for <a href="https://system.data.sqlite.org/index.html/info/9d353b0bd8">[9d353b0bd8]</a>.</li>
      <li>Refactor INSERT/UPDATE handling (in the LINQ assembly) so it can handle composite and non-integer primary keys. Fix for <a href="https://system.data.sqlite.org/index.html/info/41aea496e0">[41aea496e0]</a>.</li>
      <li>Change the base type for the SQLiteConnectionFlags enumeration to long integer.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Add extended return codes to the SQLiteErrorCode enumeration. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/71bedaca19">[71bedaca19]</a>.&nbsp;<b>** Potentially Incompatible Change **</b></li>
      <li>Improve exception handling in all native callbacks implemented in the SQLiteConnection class.</li>
      <li>Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.</li>
      <li>Add &quot;Recursive Triggers&quot; connection string property to enable or disable the recursive trigger capability. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/3a82ee635b">[3a82ee635b]</a>.</li>
      <li>Add NoDefaultFlags connection string property to prevent the default connection flags from being used. Pursuant to <a href="https://system.data.sqlite.org/index.html/info/964063da16">[964063da16]</a>.</li>
      <li>Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.</li>

Changes to Setup/data/verify.lst.

773
774
775
776
777
778
779

780
781
782
783
784
785
786
  Tests/tkt-3113734605.eagle
  Tests/tkt-343d392b51.eagle
  Tests/tkt-3567020edf.eagle
  Tests/tkt-393d954be0.eagle
  Tests/tkt-3aa50d8413.eagle
  Tests/tkt-3c00ec5b52.eagle
  Tests/tkt-3e783eecbe.eagle

  Tests/tkt-448d663d11.eagle
  Tests/tkt-47c6fa04d3.eagle
  Tests/tkt-47f4bac575.eagle
  Tests/tkt-48a6b8e4ca.eagle
  Tests/tkt-4a791e70ab.eagle
  Tests/tkt-544dba0a2f.eagle
  Tests/tkt-56b42d99c1.eagle







>







773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
  Tests/tkt-3113734605.eagle
  Tests/tkt-343d392b51.eagle
  Tests/tkt-3567020edf.eagle
  Tests/tkt-393d954be0.eagle
  Tests/tkt-3aa50d8413.eagle
  Tests/tkt-3c00ec5b52.eagle
  Tests/tkt-3e783eecbe.eagle
  Tests/tkt-41aea496e0.eagle
  Tests/tkt-448d663d11.eagle
  Tests/tkt-47c6fa04d3.eagle
  Tests/tkt-47f4bac575.eagle
  Tests/tkt-48a6b8e4ca.eagle
  Tests/tkt-4a791e70ab.eagle
  Tests/tkt-544dba0a2f.eagle
  Tests/tkt-56b42d99c1.eagle

Changes to System.Data.SQLite.Linq/SQL Generation/DmlSqlGenerator.cs.

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

















































































166
167
168
169
170
171
172
...
186
187
188
189
190
191
192




193
194
195
196
197
198
199
200
201
...
208
209
210
211
212
213
214

215
216




















































217
218
219
220
221
222
223
224
225
226

227
228





229
230
231

232
233
234
235
236
237
238













239














240
241
242












243
244
245
246
247
248
249

      // where c1 = ..., c2 = ...
      commandText.Append("WHERE ");
      tree.Predicate.Accept(translator);
      commandText.AppendLine(";");

      // generate returning sql
      GenerateReturningSql(commandText, tree, translator, tree.Returning);

      parameters = translator.Parameters;
      return commandText.ToString();
    }

    internal static string GenerateDeleteSql(DbDeleteCommandTree tree, out List<DbParameter> parameters)
    {
................................................................................
      }
      else // No columns specified.  Insert an empty row containing default values by inserting null into the rowid
      {
        commandText.AppendLine(" DEFAULT VALUES;");
      }

      // generate returning sql
      GenerateReturningSql(commandText, tree, translator, tree.Returning);

      parameters = translator.Parameters;
      return commandText.ToString();
    }

    // Generates T-SQL describing a member
    // Requires: member must belong to an entity type (a safe requirement for DML
    // SQL gen, where we only access table columns)
    private static string GenerateMemberTSql(EdmMember member)
    {
      return SqlGenerator.QuoteIdentifier(member.Name);
    }


















































































    /// <summary>
    /// Generates SQL fragment returning server-generated values.
    /// Requires: translator knows about member values so that we can figure out
    /// how to construct the key predicate.
    /// <code>
    /// Sample SQL:
................................................................................
    /// </summary>
    /// <param name="commandText">Builder containing command text</param>
    /// <param name="tree">Modification command tree</param>
    /// <param name="translator">Translator used to produce DML SQL statement
    /// for the tree</param>
    /// <param name="returning">Returning expression. If null, the method returns
    /// immediately without producing a SELECT statement.</param>




    private static void GenerateReturningSql(StringBuilder commandText, DbModificationCommandTree tree,
        ExpressionTranslator translator, DbExpression returning)
    {
      // Nothing to do if there is no Returning expression
      if (null == returning) { return; }

      // select
      commandText.Append("SELECT ");
      returning.Accept(translator);
................................................................................

      // where
#if USE_INTEROP_DLL && INTEROP_EXTENSION_FUNCTIONS
      commandText.Append("WHERE last_rows_affected() > 0");
#else
      commandText.Append("WHERE changes() > 0");
#endif

      EntitySetBase table = ((DbScanExpression)tree.Target.Expression).Target;
      bool identity = false;




















































      foreach (EdmMember keyMember in table.ElementType.KeyMembers)
      {
        commandText.Append(" AND ");
        commandText.Append(GenerateMemberTSql(keyMember));
        commandText.Append(" = ");

        // retrieve member value sql. the translator remembers member values
        // as it constructs the DML statement (which precedes the "returning"
        // SQL)
        DbParameter value;

        if (translator.MemberValues.TryGetValue(keyMember, out value))
        {





          commandText.Append(value.ParameterName);
        }
        else

        {
          // if no value is registered for the key member, it means it is an identity
          // which can be retrieved using the scope_identity() function
          if (identity)
          {
            // there can be only one server generated key
            throw new NotSupportedException(string.Format("Server generated keys are only supported for identity columns. More than one key column is marked as server generated in table '{0}'.", table.Name));













          }














          commandText.AppendLine("last_insert_rowid()");
          identity = true;
        }












      }
      commandText.AppendLine(";");
    }

    /// <summary>
    /// Lightweight expression translator for DML expression trees, which have constrained
    /// scope and support.







|







 







|












>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>

|







 







>

<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|

|
|
|
|
>
|
|
>
>
>
>
>
|
|
<
>
|
<
<
<
<
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
|
>
>
>
>
>
>
>
>
>
>
>
>







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
...
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
...
293
294
295
296
297
298
299
300
301

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

374
375






376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404

405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424

      // where c1 = ..., c2 = ...
      commandText.Append("WHERE ");
      tree.Predicate.Accept(translator);
      commandText.AppendLine(";");

      // generate returning sql
      GenerateReturningSql(commandText, tree, translator, tree.Returning, false);

      parameters = translator.Parameters;
      return commandText.ToString();
    }

    internal static string GenerateDeleteSql(DbDeleteCommandTree tree, out List<DbParameter> parameters)
    {
................................................................................
      }
      else // No columns specified.  Insert an empty row containing default values by inserting null into the rowid
      {
        commandText.AppendLine(" DEFAULT VALUES;");
      }

      // generate returning sql
      GenerateReturningSql(commandText, tree, translator, tree.Returning, true);

      parameters = translator.Parameters;
      return commandText.ToString();
    }

    // Generates T-SQL describing a member
    // Requires: member must belong to an entity type (a safe requirement for DML
    // SQL gen, where we only access table columns)
    private static string GenerateMemberTSql(EdmMember member)
    {
      return SqlGenerator.QuoteIdentifier(member.Name);
    }

    /// <summary>
    /// This method attempts to determine if the specified table has an integer
    /// primary key (i.e. "rowid").  If so, it sets the
    /// <paramref name="primaryKeyMember" /> parameter to the right
    /// <see cref="EdmMember" />; otherwise, the
    /// <paramref name="primaryKeyMember" /> parameter is set to null.
    /// </summary>
    /// <param name="table">The table to check.</param>
    /// <param name="keyMembers">
    /// The collection of key members.  An attempt is always made to set this
    /// parameter to a valid value.
    /// </param>
    /// <param name="primaryKeyMember">
    /// The <see cref="EdmMember" /> that represents the integer primary key
    /// -OR- null if no such <see cref="EdmMember" /> exists.
    /// </param>
    /// <returns>
    /// Non-zero if the specified table has an integer primary key.
    /// </returns>
    private static bool IsIntegerPrimaryKey(
        EntitySetBase table,
        out ReadOnlyMetadataCollection<EdmMember> keyMembers,
        out EdmMember primaryKeyMember
        )
    {
        keyMembers = table.ElementType.KeyMembers;

        if (keyMembers.Count == 1) /* NOTE: The "rowid" only? */
        {
            EdmMember keyMember = keyMembers[0];
            PrimitiveTypeKind typeKind;

            if (MetadataHelpers.TryGetPrimitiveTypeKind(
                    keyMember.TypeUsage, out typeKind) &&
                (typeKind == PrimitiveTypeKind.Int64))
            {
                primaryKeyMember = keyMember;
                return true;
            }
        }

        primaryKeyMember = null;
        return false;
    }

    /// <summary>
    /// This method attempts to determine if all the specified key members have
    /// values available.
    /// </summary>
    /// <param name="translator">
    /// The <see cref="ExpressionTranslator" /> to use.
    /// </param>
    /// <param name="keyMembers">
    /// The collection of key members to check.
    /// </param>
    /// <param name="missingKeyMember">
    /// The first missing key member that is found.  This is only set to a valid
    /// value if the method is returning false.
    /// </param>
    /// <returns>
    /// Non-zero if all key members have values; otherwise, zero.
    /// </returns>
    private static bool DoAllKeyMembersHaveValues(
        ExpressionTranslator translator,
        ReadOnlyMetadataCollection<EdmMember> keyMembers,
        out EdmMember missingKeyMember
        )
    {
        foreach (EdmMember keyMember in keyMembers)
        {
            if (!translator.MemberValues.ContainsKey(keyMember))
            {
                missingKeyMember = keyMember;
                return false;
            }
        }

        missingKeyMember = null;
        return true;
    }

    /// <summary>
    /// Generates SQL fragment returning server-generated values.
    /// Requires: translator knows about member values so that we can figure out
    /// how to construct the key predicate.
    /// <code>
    /// Sample SQL:
................................................................................
    /// </summary>
    /// <param name="commandText">Builder containing command text</param>
    /// <param name="tree">Modification command tree</param>
    /// <param name="translator">Translator used to produce DML SQL statement
    /// for the tree</param>
    /// <param name="returning">Returning expression. If null, the method returns
    /// immediately without producing a SELECT statement.</param>
    /// <param name="wasInsert">
    /// Non-zero if this method is being called as part of processing an INSERT;
    /// otherwise (e.g. UPDATE), zero.
    /// </param>
    private static void GenerateReturningSql(StringBuilder commandText, DbModificationCommandTree tree,
        ExpressionTranslator translator, DbExpression returning, bool wasInsert)
    {
      // Nothing to do if there is no Returning expression
      if (null == returning) { return; }

      // select
      commandText.Append("SELECT ");
      returning.Accept(translator);
................................................................................

      // where
#if USE_INTEROP_DLL && INTEROP_EXTENSION_FUNCTIONS
      commandText.Append("WHERE last_rows_affected() > 0");
#else
      commandText.Append("WHERE changes() > 0");
#endif

      EntitySetBase table = ((DbScanExpression)tree.Target.Expression).Target;

      ReadOnlyMetadataCollection<EdmMember> keyMembers;
      EdmMember primaryKeyMember;
      EdmMember missingKeyMember;

      // Model Types can be (at the time of this implementation):
      //      Binary, Boolean, Byte, DateTime, Decimal, Double, Guid, Int16,
      //      Int32, Int64,Single, String
      if (IsIntegerPrimaryKey(table, out keyMembers, out primaryKeyMember))
      {
          //
          // NOTE: This must be an INTEGER PRIMARY KEY (i.e. "rowid") table.
          //
          commandText.Append(" AND ");
          commandText.Append(GenerateMemberTSql(primaryKeyMember));
          commandText.Append(" = ");

          DbParameter value;

          if (translator.MemberValues.TryGetValue(primaryKeyMember, out value))
          {
              //
              // NOTE: Use the integer primary key value that was specified as
              //       part the associated INSERT/UPDATE statement.
              //
              commandText.Append(value.ParameterName);
          }
          else if (wasInsert)
          {
              //
              // NOTE: This was part of an INSERT statement and we know the table
              //       has an integer primary key.  This should not fail unless
              //       something (e.g. a trigger) causes the last_insert_rowid()
              //       function to return an incorrect result.
              //
              commandText.AppendLine("last_insert_rowid()");
          }
          else /* NOT-REACHED? */
          {
              //
              // NOTE: We cannot simply use the "rowid" at this point because:
              //
              //       1. The last_insert_rowid() function is only valid after
              //          an INSERT and this was an UPDATE.
              //
              throw new NotSupportedException(String.Format(
                  "Missing value for INSERT key member '{0}' in table '{1}'.",
                   (primaryKeyMember != null) ? primaryKeyMember.Name : "<unknown>",
                   table.Name));
          }
      }
      else if (DoAllKeyMembersHaveValues(translator, keyMembers, out missingKeyMember))
      {
          foreach (EdmMember keyMember in keyMembers)
          {
              commandText.Append(" AND ");
              commandText.Append(GenerateMemberTSql(keyMember));
              commandText.Append(" = ");

              // Retrieve member value SQL. the translator remembers member values
              // as it constructs the DML statement (which precedes the "returning"
              // SQL).
              DbParameter value;

              if (translator.MemberValues.TryGetValue(keyMember, out value))
              {
                  //
                  // NOTE: Use the primary key value that was specified as part the
                  //       associated INSERT/UPDATE statement.  This also applies
                  //       to composite primary keys.
                  //
                  commandText.Append(value.ParameterName);
              }

              else /* NOT-REACHED? */
              {






                  //
                  // NOTE: We cannot simply use the "rowid" at this point because:
                  //
                  //       1. This associated INSERT/UPDATE statement appeared to
                  //          have all the key members availab;e however, there
                  //          appears to be an inconsistency.  This is an internal
                  //          error and should be thrown.
                  //
                  throw new NotSupportedException(String.Format(
                      "Missing value for {0} key member '{1}' in table '{2}' " +
                      "(internal).", wasInsert ? "INSERT" : "UPDATE",
                      (keyMember != null) ? keyMember.Name : "<unknown>",
                      table.Name));
              }
          }
      }
      else if (wasInsert) /* NOT-REACHED? */
      {
          //
          // NOTE: This was part of an INSERT statement; try using the "rowid"
          //       column to fetch the most recently inserted row.  This may
          //       still fail if the table is a WITHOUT ROWID table -OR-
          //       something (e.g. a trigger) causes the last_insert_rowid()
          //       function to return an incorrect result.
          //
          commandText.Append(" AND ");
          commandText.Append(SqlGenerator.QuoteIdentifier("rowid"));
          commandText.Append(" = ");
          commandText.AppendLine("last_insert_rowid()");

      }
      else /* NOT-REACHED? */
      {
          //
          // NOTE: We cannot simply use the "rowid" at this point because:
          //
          //       1. The last_insert_rowid() function is only valid after
          //          an INSERT and this was an UPDATE.
          //
          throw new NotSupportedException(String.Format(
              "Missing value for UPDATE key member '{0}' in table '{1}'.",
               (missingKeyMember != null) ? missingKeyMember.Name : "<unknown>",
               table.Name));
      }
      commandText.AppendLine(";");
    }

    /// <summary>
    /// Lightweight expression translator for DML expression trees, which have constrained
    /// scope and support.

Added Tests/tkt-41aea496e0.eagle.



































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
###############################################################################
#
# tkt-41aea496e0.eagle --
#
# Written by Joe Mistachkin.
# Released to the public domain, use at your own risk!
#
###############################################################################

package require Eagle
package require Eagle.Library
package require Eagle.Test

runTestPrologue

###############################################################################

package require System.Data.SQLite.Test
runSQLiteTestPrologue
runSQLiteTestFilesPrologue

###############################################################################

runTest {test tkt-41aea496e0-1.1 {LINQ non-rowid primary key support} -body {
  #
  # NOTE: Re-copy the reference database file used for this unit test to the
  #       build directory in case it has been changed by a previous test run.
  #
  file copy -force $northwindEfDbFile \
      [file join [getBuildDirectory] [file tail $northwindEfDbFile]]

  set result [list]
  set output ""

  set code [catch {
    testClrExec $testLinqExeFile [list -eventflags Wait -directory \
        [file dirname $testLinqExeFile] -nocarriagereturns -stdout output \
        -success 0] -complexprimarykey
  } error]

  tlog "---- BEGIN STDOUT OUTPUT\n"
  tlog $output
  tlog "\n---- END STDOUT OUTPUT\n"

  lappend result $code

  if {$code == 0} then {
    lappend result [string trim $output]
  } else {
    lappend result [string trim $error]
  }

  set result
} -cleanup {
  unset -nocomplain code output error result
} -constraints {eagle monoToDo SQLite file_System.Data.SQLite.dll testExec\
file_System.Data.SQLite.Linq.dll file_testlinq.exe file_northwindEF.db} \
-result {0 {inserted 2
updated 2}}}

###############################################################################

runSQLiteTestFilesEpilogue
runSQLiteTestEpilogue
runTestEpilogue

Changes to Tests/tkt-9d353b0bd8.eagle.

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    lappend result [string trim $error]
  }

  set result
} -cleanup {
  unset -nocomplain code output error result
} -constraints {eagle monoToDo SQLite file_System.Data.SQLite.dll testExec\
file_System.Data.SQLite.Linq.dll file_testlinq.exe file_northwindEF.db\
System.Data.SQLite.dll_v4.0.30319} -result {0 {inserted 1}}}

###############################################################################

runSQLiteTestFilesEpilogue
runSQLiteTestEpilogue
runTestEpilogue







|
|






50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    lappend result [string trim $error]
  }

  set result
} -cleanup {
  unset -nocomplain code output error result
} -constraints {eagle monoToDo SQLite file_System.Data.SQLite.dll testExec\
file_System.Data.SQLite.Linq.dll file_testlinq.exe file_northwindEF.db} \
-result {0 {inserted 1}}}

###############################################################################

runSQLiteTestFilesEpilogue
runSQLiteTestEpilogue
runTestEpilogue

Changes to readme.htm.

216
217
218
219
220
221
222

223
224
225
226
227
228
229
    <li>Add full support for Visual Studio 2015 and the .NET Framework 4.6.</li>
    <li>Add support for creating custom SQL functions using delegates.</li>
    <li>Implement the Substring method for LINQ using the &quot;substr&quot; core SQL function.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the second argument to Math.Round when using LINQ.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the pre-existing flags for connections during the Open method. Fix for [964063da16].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].</li>

    <li>Change the base type for the SQLiteConnectionFlags enumeration to long integer.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Add extended return codes to the SQLiteErrorCode enumeration. Pursuant to [71bedaca19].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Improve exception handling in all native callbacks implemented in the SQLiteConnection class.</li>
    <li>Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.</li>
    <li>Add &quot;Recursive Triggers&quot; connection string property to enable or disable the recursive trigger capability. Pursuant to [3a82ee635b].</li>
    <li>Add NoDefaultFlags connection string property to prevent the default connection flags from being used. Pursuant to [964063da16].</li>
    <li>Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.</li>







>







216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    <li>Add full support for Visual Studio 2015 and the .NET Framework 4.6.</li>
    <li>Add support for creating custom SQL functions using delegates.</li>
    <li>Implement the Substring method for LINQ using the &quot;substr&quot; core SQL function.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the second argument to Math.Round when using LINQ.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the pre-existing flags for connections during the Open method. Fix for [964063da16].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].</li>
    <li>Refactor INSERT/UPDATE handling (in the LINQ assembly) so it can handle composite and non-integer primary keys. Fix for [41aea496e0].</li>
    <li>Change the base type for the SQLiteConnectionFlags enumeration to long integer.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Add extended return codes to the SQLiteErrorCode enumeration. Pursuant to [71bedaca19].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Improve exception handling in all native callbacks implemented in the SQLiteConnection class.</li>
    <li>Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.</li>
    <li>Add &quot;Recursive Triggers&quot; connection string property to enable or disable the recursive trigger capability. Pursuant to [3a82ee635b].</li>
    <li>Add NoDefaultFlags connection string property to prevent the default connection flags from being used. Pursuant to [964063da16].</li>
    <li>Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.</li>

Changes to testlinq/Program.cs.

2
3
4
5
6
7
8


9
10
11
12
13
14
15
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
...
196
197
198
199
200
201
202




203
204
205
206
207
208
209
...
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570


571
572
573
574
575
576
577
578
579
580
581
582








583
584
585
586
587
588
589








590
591
592
593
594
595
596
...
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
...
750
751
752
753
754
755
756










































































































757
758
759
760
761
762
763
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 *
 * Released to the public domain, use at your own risk!
 ********************************************************/

using System;


using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Transactions;

................................................................................

                              return 1;
                          }
                      }

                      return EFTransactionTest(value);
                  }
#if NET_40 || NET_45 || NET_451 || NET_46
              case "insert":
                  {
                      return InsertTest();
                  }
#endif
              case "update":
                  {
                      return UpdateTest();
                  }
              case "binaryguid":
                  {
                      bool value = false;
................................................................................
#endif
#if NET_40 || NET_45 || NET_451 || NET_46
              case "round":
                  {
                      return RoundTest();
                  }
#endif




              default:
                  {
                      Console.WriteLine("unknown test \"{0}\"", arg);
                      return 1;
                  }
          }
      }
................................................................................
#endif
              }
          }

          return 0;
      }

#if NET_40 || NET_45 || NET_451 || NET_46
      //
      // NOTE: Used to test the INSERT fix (i.e. an extra semi-colon in
      //       the SQL statement after the actual INSERT statement in
      //       the follow-up SELECT statement).
      //
      private static int InsertTest()
      {
          using (northwindEFEntities db = new northwindEFEntities())
          {


              int[] counts = { 0 };

              //
              // NOTE: *REQUIRED* This is required so that the
              //       Entity Framework is prevented from opening
              //       multiple connections to the underlying SQLite
              //       database (i.e. which would result in multiple
              //       IMMEDIATE transactions, thereby failing [later
              //       on] with locking errors).
              //
              db.Connection.Open();









              OrderDetails newOrderDetails = new OrderDetails();

              newOrderDetails.OrderID = 10248;
              newOrderDetails.ProductID = 1;
              newOrderDetails.UnitPrice = (decimal)1.23;
              newOrderDetails.Quantity = 1;
              newOrderDetails.Discount = 0.0f;









              db.AddObject("OrderDetails", newOrderDetails);

              try
              {
                  db.SaveChanges();
                  counts[0]++;
................................................................................
              }

              Console.WriteLine("inserted {0}", counts[0]);
          }

          return 0;
      }
#endif

      //
      // NOTE: Used to test the UPDATE fix (i.e. the missing semi-colon
      //       in the SQL statement between the actual UPDATE statement
      //       and the follow-up SELECT statement).
      //
      private static int UpdateTest()
................................................................................
          Environment.SetEnvironmentVariable(
              "AppendManifestToken_SQLiteProviderManifest",
              null);

          return 0;
      }
#endif











































































































      private static int DateTimeTest()
      {
          using (northwindEFEntities db = new northwindEFEntities())
          {
              DateTime dateTime = new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Local);
              int c1 = db.Orders.Where(i => i.OrderDate == new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Local)).Count();







>
>







 







<




<







 







>
>
>
>







 







<









>
>












>
>
>
>
>
>
>
>


|
|



>
>
>
>
>
>
>
>







 







<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
144
145
146
147
148
149
150

151
152
153
154

155
156
157
158
159
160
161
...
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
...
558
559
560
561
562
563
564

565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
...
626
627
628
629
630
631
632

633
634
635
636
637
638
639
...
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 *
 * Released to the public domain, use at your own risk!
 ********************************************************/

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Transactions;

................................................................................

                              return 1;
                          }
                      }

                      return EFTransactionTest(value);
                  }

              case "insert":
                  {
                      return InsertTest();
                  }

              case "update":
                  {
                      return UpdateTest();
                  }
              case "binaryguid":
                  {
                      bool value = false;
................................................................................
#endif
#if NET_40 || NET_45 || NET_451 || NET_46
              case "round":
                  {
                      return RoundTest();
                  }
#endif
              case "complexprimarykey":
                  {
                      return ComplexPrimaryKeyTest();
                  }
              default:
                  {
                      Console.WriteLine("unknown test \"{0}\"", arg);
                      return 1;
                  }
          }
      }
................................................................................
#endif
              }
          }

          return 0;
      }


      //
      // NOTE: Used to test the INSERT fix (i.e. an extra semi-colon in
      //       the SQL statement after the actual INSERT statement in
      //       the follow-up SELECT statement).
      //
      private static int InsertTest()
      {
          using (northwindEFEntities db = new northwindEFEntities())
          {
              long orderId = 10248;
              long productId = 1;
              int[] counts = { 0 };

              //
              // NOTE: *REQUIRED* This is required so that the
              //       Entity Framework is prevented from opening
              //       multiple connections to the underlying SQLite
              //       database (i.e. which would result in multiple
              //       IMMEDIATE transactions, thereby failing [later
              //       on] with locking errors).
              //
              db.Connection.Open();

              KeyValuePair<string, object> orderIdPair =
                  new KeyValuePair<string, object>("OrderID", orderId);

              KeyValuePair<string, object> productIdPair =
                  new KeyValuePair<string, object>("ProductID", productId);

              /////////////////////////////////////////////////////////////////

              OrderDetails newOrderDetails = new OrderDetails();

              newOrderDetails.OrderID = orderId;
              newOrderDetails.ProductID = productId;
              newOrderDetails.UnitPrice = (decimal)1.23;
              newOrderDetails.Quantity = 1;
              newOrderDetails.Discount = 0.0f;

              newOrderDetails.OrdersReference.EntityKey = new EntityKey(
                  "northwindEFEntities.Orders",
                  new KeyValuePair<string, object>[] { orderIdPair });

              newOrderDetails.ProductsReference.EntityKey = new EntityKey(
                  "northwindEFEntities.Products",
                  new KeyValuePair<string, object>[] { productIdPair });

              db.AddObject("OrderDetails", newOrderDetails);

              try
              {
                  db.SaveChanges();
                  counts[0]++;
................................................................................
              }

              Console.WriteLine("inserted {0}", counts[0]);
          }

          return 0;
      }


      //
      // NOTE: Used to test the UPDATE fix (i.e. the missing semi-colon
      //       in the SQL statement between the actual UPDATE statement
      //       and the follow-up SELECT statement).
      //
      private static int UpdateTest()
................................................................................
          Environment.SetEnvironmentVariable(
              "AppendManifestToken_SQLiteProviderManifest",
              null);

          return 0;
      }
#endif

      private static int ComplexPrimaryKeyTest()
      {
          using (northwindEFEntities db = new northwindEFEntities())
          {
              long orderId = 10248;
              long productId = 1;
              int[] counts = { 0, 0 };

              //
              // NOTE: *REQUIRED* This is required so that the
              //       Entity Framework is prevented from opening
              //       multiple connections to the underlying SQLite
              //       database (i.e. which would result in multiple
              //       IMMEDIATE transactions, thereby failing [later
              //       on] with locking errors).
              //
              db.Connection.Open();

              KeyValuePair<string, object> orderIdPair =
                  new KeyValuePair<string, object>("OrderID", orderId);

              KeyValuePair<string, object> productIdPair =
                  new KeyValuePair<string, object>("ProductID", productId);

              /////////////////////////////////////////////////////////////////

              OrderDetails newOrderDetails = new OrderDetails();

              newOrderDetails.OrderID = orderId;
              newOrderDetails.ProductID = productId;
              newOrderDetails.UnitPrice = (decimal)1.23;
              newOrderDetails.Quantity = 1;
              newOrderDetails.Discount = 0.0f;

              newOrderDetails.OrdersReference.EntityKey = new EntityKey(
                  "northwindEFEntities.Orders",
                  new KeyValuePair<string, object>[] { orderIdPair });

              newOrderDetails.ProductsReference.EntityKey = new EntityKey(
                  "northwindEFEntities.Products",
                  new KeyValuePair<string, object>[] { productIdPair });

              db.AddObject("OrderDetails", newOrderDetails);

              try
              {
                  db.SaveChanges();
                  counts[0]++;
              }
              catch (Exception e)
              {
                  Console.WriteLine(e);
              }
              finally
              {
                  db.AcceptAllChanges();
              }

              try
              {
                  db.Refresh(RefreshMode.StoreWins, newOrderDetails);
                  counts[0]++;
              }
              catch (Exception e)
              {
                  Console.WriteLine(e);
              }

              Console.WriteLine("inserted {0}", counts[0]);

              /////////////////////////////////////////////////////////////////

              newOrderDetails.UnitPrice = (decimal)2.34;
              newOrderDetails.Quantity = 2;
              newOrderDetails.Discount = 0.1f;

              try
              {
                  db.SaveChanges();
                  counts[1]++;
              }
              catch (Exception e)
              {
                  Console.WriteLine(e);
              }
              finally
              {
                  db.AcceptAllChanges();
              }

              try
              {
                  db.Refresh(RefreshMode.StoreWins, newOrderDetails);
                  counts[1]++;
              }
              catch (Exception e)
              {
                  Console.WriteLine(e);
              }

              Console.WriteLine("updated {0}", counts[1]);
          }

          return 0;
      }

      private static int DateTimeTest()
      {
          using (northwindEFEntities db = new northwindEFEntities())
          {
              DateTime dateTime = new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Local);
              int c1 = db.Orders.Where(i => i.OrderDate == new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Local)).Count();

Changes to www/news.wiki.

10
11
12
13
14
15
16

17
18
19
20
21
22
23
    <li>Add full support for Visual Studio 2015 and the .NET Framework 4.6.</li>
    <li>Add support for creating custom SQL functions using delegates.</li>
    <li>Implement the Substring method for LINQ using the &quot;substr&quot; core SQL function.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the second argument to Math.Round when using LINQ.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the pre-existing flags for connections during the Open method. Fix for [964063da16].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].</li>

    <li>Change the base type for the SQLiteConnectionFlags enumeration to long integer.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Add extended return codes to the SQLiteErrorCode enumeration. Pursuant to [71bedaca19].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Improve exception handling in all native callbacks implemented in the SQLiteConnection class.</li>
    <li>Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.</li>
    <li>Add &quot;Recursive Triggers&quot; connection string property to enable or disable the recursive trigger capability. Pursuant to [3a82ee635b].</li>
    <li>Add NoDefaultFlags connection string property to prevent the default connection flags from being used. Pursuant to [964063da16].</li>
    <li>Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.</li>







>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    <li>Add full support for Visual Studio 2015 and the .NET Framework 4.6.</li>
    <li>Add support for creating custom SQL functions using delegates.</li>
    <li>Implement the Substring method for LINQ using the &quot;substr&quot; core SQL function.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Prevent encrypted connections from being used with the connection pool. Pursuant to [89d3a159f1].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the second argument to Math.Round when using LINQ.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Honor the pre-existing flags for connections during the Open method. Fix for [964063da16].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].</li>
    <li>Refactor INSERT/UPDATE handling (in the LINQ assembly) so it can handle composite and non-integer primary keys. Fix for [41aea496e0].</li>
    <li>Change the base type for the SQLiteConnectionFlags enumeration to long integer.&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Add extended return codes to the SQLiteErrorCode enumeration. Pursuant to [71bedaca19].&nbsp;<b>** Potentially Incompatible Change **</b></li>
    <li>Improve exception handling in all native callbacks implemented in the SQLiteConnection class.</li>
    <li>Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.</li>
    <li>Add &quot;Recursive Triggers&quot; connection string property to enable or disable the recursive trigger capability. Pursuant to [3a82ee635b].</li>
    <li>Add NoDefaultFlags connection string property to prevent the default connection flags from being used. Pursuant to [964063da16].</li>
    <li>Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.</li>