From d2c47478232f41d98ccb8f74dc7887e7e3df807c Mon Sep 17 00:00:00 2001 From: Nils Henrik Hals Date: Fri, 12 Feb 2021 18:16:31 +0100 Subject: [PATCH] Add Ordinal and Name Caching to SqliteDataRecord This speeds up repeated ordinal and Name access. Example existing usage could be from SqlDataReaderExtension.GetValueOrDefault or similar. Related to #24140 --- .../SqliteDataRecord.cs | 20 +++++++++++++++---- .../SqliteDataReaderTest.cs | 8 +++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteDataRecord.cs b/src/Microsoft.Data.Sqlite.Core/SqliteDataRecord.cs index c6e2bace6f7..ddbd54728ab 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteDataRecord.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteDataRecord.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -17,6 +18,8 @@ internal class SqliteDataRecord : SqliteValueReader, IDisposable private readonly SqliteConnection _connection; private byte[][]? _blobCache; private int?[]? _typeCache; + private Dictionary? _columnNameOrdinalCache; + private string?[]? _columnNameCache; private bool _stepped; private int? _rowidOrdinal; @@ -100,7 +103,7 @@ protected override T GetNull(int ordinal) public virtual string GetName(int ordinal) { - var name = sqlite3_column_name(Handle, ordinal).utf8_to_string(); + var name = _columnNameCache?[ordinal] ?? sqlite3_column_name(Handle, ordinal).utf8_to_string(); if (name == null && (ordinal < 0 || ordinal >= FieldCount)) { @@ -108,18 +111,27 @@ public virtual string GetName(int ordinal) throw new ArgumentOutOfRangeException(nameof(ordinal), ordinal, message: null); } + _columnNameCache ??= new string[FieldCount]; + _columnNameCache[ordinal] = name; + return name!; } public virtual int GetOrdinal(string name) { - for (var i = 0; i < FieldCount; i++) + if (_columnNameOrdinalCache == null) { - if (GetName(i) == name) + _columnNameOrdinalCache = new Dictionary(); + for (var i = 0; i < FieldCount; i++) { - return i; + _columnNameOrdinalCache[GetName(i)] = i; } } + + if (_columnNameOrdinalCache.TryGetValue(name, out var ordinal)) + { + return ordinal; + } // NB: Message is provided by framework throw new ArgumentOutOfRangeException(nameof(name), name, message: null); diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs index 76582ab94e5..f6cdd8c809b 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs @@ -1227,6 +1227,8 @@ public void GetName_works() using (var reader = connection.ExecuteReader("SELECT 1 AS Id;")) { Assert.Equal("Id", reader.GetName(0)); + // Repeated access might use caching. + Assert.Equal("Id", reader.GetName(0)); } } } @@ -1263,9 +1265,12 @@ public void GetOrdinal_works() { connection.Open(); - using (var reader = connection.ExecuteReader("SELECT 1 AS Id;")) + using (var reader = connection.ExecuteReader("SELECT 1 as Id, 'ÆØÅ' AS Value")) { Assert.Equal(0, reader.GetOrdinal("Id")); + Assert.Equal(1, reader.GetOrdinal("Value")); + // Repeated access may use caching + Assert.Equal(1, reader.GetOrdinal("Value")); } } } @@ -1297,6 +1302,7 @@ public void GetOrdinal_throws_when_closed() public void GetOrdinal_throws_when_non_query() => X_throws_when_non_query(r => r.GetOrdinal("dummy")); + [Fact] public void GetString_works_utf8() => GetX_works(