Skip to content

Commit cc9ddb5

Browse files
authored
Change _SqlMetaData to lazy initialize hidden column map (#521)
1 parent 4f04db7 commit cc9ddb5

File tree

5 files changed

+164
-55
lines changed

5 files changed

+164
-55
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedHelperClasses.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,13 +234,9 @@ sealed internal partial class _SqlMetaDataSet
234234
internal readonly SqlTceCipherInfoTable? cekTable; // table of "column encryption keys" used for this metadataset
235235

236236
internal _SqlMetaDataSet(int count, SqlTceCipherInfoTable? cipherTable)
237+
: this(count)
237238
{
238239
cekTable = cipherTable;
239-
_metaDataArray = new _SqlMetaData[count];
240-
for (int i = 0; i < _metaDataArray.Length; ++i)
241-
{
242-
_metaDataArray[i] = new _SqlMetaData(i);
243-
}
244240
}
245241
}
246242

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData()
276276

277277
if (null != metaData && 0 < metaData.Length)
278278
{
279-
metaDataReturn = new SmiExtendedMetaData[metaData.visibleColumns];
280-
279+
metaDataReturn = new SmiExtendedMetaData[metaData.VisibleColumnCount];
280+
int returnIndex = 0;
281281
for (int index = 0; index < metaData.Length; index++)
282282
{
283283
_SqlMetaData colMetaData = metaData[index];
@@ -316,7 +316,7 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData()
316316
length /= ADP.CharSize;
317317
}
318318

319-
metaDataReturn[index] =
319+
metaDataReturn[returnIndex] =
320320
new SmiQueryMetaData(
321321
colMetaData.type,
322322
length,
@@ -344,6 +344,8 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData()
344344
colMetaData.IsExpression,
345345
colMetaData.IsDifferentName,
346346
colMetaData.IsHidden);
347+
348+
returnIndex += 1;
347349
}
348350
}
349351
}
@@ -406,7 +408,7 @@ override public int VisibleFieldCount
406408
{
407409
return 0;
408410
}
409-
return (md.visibleColumns);
411+
return md.VisibleColumnCount;
410412
}
411413
}
412414

@@ -1141,31 +1143,6 @@ private bool TryConsumeMetaData()
11411143
Debug.Assert(!ignored, "Parser read a row token while trying to read metadata");
11421144
}
11431145

1144-
// we hide hidden columns from the user so build an internal map
1145-
// that compacts all hidden columns from the array
1146-
if (null != _metaData)
1147-
{
1148-
if (_snapshot != null && object.ReferenceEquals(_snapshot._metadata, _metaData))
1149-
{
1150-
_metaData = (_SqlMetaDataSet)_metaData.Clone();
1151-
}
1152-
1153-
_metaData.visibleColumns = 0;
1154-
1155-
Debug.Assert(null == _metaData.indexMap, "non-null metaData indexmap");
1156-
int[] indexMap = new int[_metaData.Length];
1157-
for (int i = 0; i < indexMap.Length; ++i)
1158-
{
1159-
indexMap[i] = _metaData.visibleColumns;
1160-
1161-
if (!(_metaData[i].IsHidden))
1162-
{
1163-
_metaData.visibleColumns++;
1164-
}
1165-
}
1166-
_metaData.indexMap = indexMap;
1167-
}
1168-
11691146
return true;
11701147
}
11711148

@@ -2621,11 +2598,11 @@ virtual public int GetSqlValues(object[] values)
26212598

26222599
SetTimeout(_defaultTimeoutMilliseconds);
26232600

2624-
int copyLen = (values.Length < _metaData.visibleColumns) ? values.Length : _metaData.visibleColumns;
2601+
int copyLen = (values.Length < _metaData.VisibleColumnCount) ? values.Length : _metaData.VisibleColumnCount;
26252602

26262603
for (int i = 0; i < copyLen; i++)
26272604
{
2628-
values[_metaData.indexMap[i]] = GetSqlValueInternal(i);
2605+
values[i] = GetSqlValueInternal(_metaData.GetVisibleColumnIndex(i));
26292606
}
26302607
return copyLen;
26312608
}
@@ -2964,7 +2941,7 @@ override public int GetValues(object[] values)
29642941

29652942
CheckMetaDataIsReady();
29662943

2967-
int copyLen = (values.Length < _metaData.visibleColumns) ? values.Length : _metaData.visibleColumns;
2944+
int copyLen = (values.Length < _metaData.VisibleColumnCount) ? values.Length : _metaData.VisibleColumnCount;
29682945
int maximumColumn = copyLen - 1;
29692946

29702947
SetTimeout(_defaultTimeoutMilliseconds);
@@ -2982,12 +2959,19 @@ override public int GetValues(object[] values)
29822959
for (int i = 0; i < copyLen; i++)
29832960
{
29842961
// Get the usable, TypeSystem-compatible value from the internal buffer
2985-
values[_metaData.indexMap[i]] = GetValueFromSqlBufferInternal(_data[i], _metaData[i]);
2962+
int fieldIndex = _metaData.GetVisibleColumnIndex(i);
2963+
values[i] = GetValueFromSqlBufferInternal(_data[fieldIndex], _metaData[fieldIndex]);
29862964

29872965
// If this is sequential access, then we need to wipe the internal buffer
29882966
if ((sequentialAccess) && (i < maximumColumn))
29892967
{
2990-
_data[i].Clear();
2968+
_data[fieldIndex].Clear();
2969+
if (fieldIndex > i && fieldIndex>0)
2970+
{
2971+
// if we jumped an index forward because of a hidden column see if the buffer before the
2972+
// current one was populated by the seek forward and clear it if it was
2973+
_data[fieldIndex - 1].Clear();
2974+
}
29912975
}
29922976
}
29932977

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4447,7 +4447,6 @@ internal bool TryProcessAltMetaData(int cColumns, TdsParserStateObject stateObj,
44474447
metaData = null;
44484448

44494449
_SqlMetaDataSet altMetaDataSet = new _SqlMetaDataSet(cColumns, null);
4450-
int[] indexMap = new int[cColumns];
44514450

44524451
if (!stateObj.TryReadUInt16(out altMetaDataSet.id))
44534452
{
@@ -4491,13 +4490,8 @@ internal bool TryProcessAltMetaData(int cColumns, TdsParserStateObject stateObj,
44914490
{
44924491
return false;
44934492
}
4494-
4495-
indexMap[i] = i;
44964493
}
44974494

4498-
altMetaDataSet.indexMap = indexMap;
4499-
altMetaDataSet.visibleColumns = cColumns;
4500-
45014495
metaData = altMetaDataSet;
45024496
return true;
45034497
}

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -542,14 +542,17 @@ public object Clone()
542542
internal sealed partial class _SqlMetaDataSet
543543
{
544544
internal ushort id; // for altrow-columns only
545-
internal int[] indexMap;
546-
internal int visibleColumns;
545+
547546
internal DataTable schemaTable;
548547
private readonly _SqlMetaData[] _metaDataArray;
549548
internal ReadOnlyCollection<DbColumn> dbColumnSchema;
550549

550+
private int _hiddenColumnCount;
551+
private int[] _visibleColumnMap;
552+
551553
internal _SqlMetaDataSet(int count)
552554
{
555+
_hiddenColumnCount = -1;
553556
_metaDataArray = new _SqlMetaData[count];
554557
for (int i = 0; i < _metaDataArray.Length; ++i)
555558
{
@@ -559,11 +562,10 @@ internal _SqlMetaDataSet(int count)
559562

560563
private _SqlMetaDataSet(_SqlMetaDataSet original)
561564
{
562-
this.id = original.id;
563-
// although indexMap is not immutable, in practice it is initialized once and then passed around
564-
this.indexMap = original.indexMap;
565-
this.visibleColumns = original.visibleColumns;
566-
this.dbColumnSchema = original.dbColumnSchema;
565+
id = original.id;
566+
_hiddenColumnCount = original._hiddenColumnCount;
567+
_visibleColumnMap = original._visibleColumnMap;
568+
dbColumnSchema = original.dbColumnSchema;
567569
if (original._metaDataArray == null)
568570
{
569571
_metaDataArray = null;
@@ -586,6 +588,18 @@ internal int Length
586588
}
587589
}
588590

591+
internal int VisibleColumnCount
592+
{
593+
get
594+
{
595+
if (_hiddenColumnCount == -1)
596+
{
597+
SetupHiddenColumns();
598+
}
599+
return Length - _hiddenColumnCount;
600+
}
601+
}
602+
589603
internal _SqlMetaData this[int index]
590604
{
591605
get
@@ -599,10 +613,54 @@ internal _SqlMetaData this[int index]
599613
}
600614
}
601615

602-
public object Clone()
616+
public int GetVisibleColumnIndex(int index)
617+
{
618+
if (_hiddenColumnCount == -1)
619+
{
620+
SetupHiddenColumns();
621+
}
622+
if (_visibleColumnMap is null)
623+
{
624+
return index;
625+
}
626+
else
627+
{
628+
return _visibleColumnMap[index];
629+
}
630+
}
631+
632+
public _SqlMetaDataSet Clone()
603633
{
604634
return new _SqlMetaDataSet(this);
605635
}
636+
637+
private void SetupHiddenColumns()
638+
{
639+
int hiddenColumnCount = 0;
640+
for (int index = 0; index < Length; index++)
641+
{
642+
if (_metaDataArray[index].IsHidden)
643+
{
644+
hiddenColumnCount += 1;
645+
}
646+
}
647+
648+
if (hiddenColumnCount > 0)
649+
{
650+
int[] visibleColumnMap = new int[Length - hiddenColumnCount];
651+
int mapIndex = 0;
652+
for (int metaDataIndex = 0; metaDataIndex < Length; metaDataIndex++)
653+
{
654+
if (!_metaDataArray[metaDataIndex].IsHidden)
655+
{
656+
visibleColumnMap[mapIndex] = metaDataIndex;
657+
mapIndex += 1;
658+
}
659+
}
660+
_visibleColumnMap = visibleColumnMap;
661+
}
662+
_hiddenColumnCount = hiddenColumnCount;
663+
}
606664
}
607665

608666
internal sealed class _SqlMetaDataSetCollection
@@ -649,10 +707,10 @@ internal _SqlMetaDataSet GetAltMetaData(int id)
649707
public object Clone()
650708
{
651709
_SqlMetaDataSetCollection result = new _SqlMetaDataSetCollection();
652-
result.metaDataSet = metaDataSet == null ? null : (_SqlMetaDataSet)metaDataSet.Clone();
710+
result.metaDataSet = metaDataSet == null ? null : metaDataSet.Clone();
653711
foreach (_SqlMetaDataSet set in _altMetaDataSetArray)
654712
{
655-
result._altMetaDataSetArray.Add((_SqlMetaDataSet)set.Clone());
713+
result._altMetaDataSetArray.Add(set.Clone());
656714
}
657715
return result;
658716
}

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.Data;
78
using System.Text;
@@ -225,5 +226,81 @@ private static bool IsColumnBitSet(SqlConnection con, string selectQuery, int in
225226
}
226227
return columnSetPresent;
227228
}
229+
230+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
231+
public static void CheckHiddenColumns()
232+
{
233+
// hidden columns can be found by using CommandBehavior.KeyInfo or at the sql level
234+
// by using the FOR BROWSE option. These features return the column information requested and
235+
// also include any key information required to be able to find the row containing the data
236+
// requested. The additional key information is provided as hidden columns and can be seen using
237+
// the difference between VisibleFieldCount and FieldCount on the reader
238+
239+
string tempTableName = DataTestUtility.GenerateObjectName();
240+
241+
string createQuery = $@"
242+
create table [{tempTableName}] (
243+
user_id int not null identity(1,1),
244+
first_name varchar(100) null,
245+
last_name varchar(100) null);
246+
247+
alter table [{tempTableName}] add constraint pk_{tempTableName}_user_id primary key (user_id);
248+
249+
insert into [{tempTableName}] (first_name,last_name) values ('Joe','Smith')
250+
";
251+
252+
string dataQuery = $@"select first_name, last_name from [{tempTableName}]";
253+
254+
255+
int fieldCount = 0;
256+
int visibleFieldCount = 0;
257+
Type[] types = null;
258+
string[] names = null;
259+
260+
using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString))
261+
{
262+
connection.Open();
263+
264+
try
265+
{
266+
using (SqlCommand createCommand = new SqlCommand(createQuery, connection))
267+
{
268+
createCommand.ExecuteNonQuery();
269+
}
270+
271+
using (SqlCommand queryCommand = new SqlCommand(dataQuery, connection))
272+
{
273+
using (SqlDataReader reader = queryCommand.ExecuteReader(CommandBehavior.KeyInfo))
274+
{
275+
fieldCount = reader.FieldCount;
276+
visibleFieldCount = reader.VisibleFieldCount;
277+
types = new Type[fieldCount];
278+
names = new string[fieldCount];
279+
for (int index = 0; index < fieldCount; index++)
280+
{
281+
types[index] = reader.GetFieldType(index);
282+
names[index] = reader.GetName(index);
283+
}
284+
}
285+
}
286+
}
287+
finally
288+
{
289+
DataTestUtility.DropTable(connection, tempTableName);
290+
}
291+
}
292+
293+
Assert.Equal(3, fieldCount);
294+
Assert.Equal(2, visibleFieldCount);
295+
Assert.NotNull(types);
296+
Assert.NotNull(names);
297+
298+
// requested fields
299+
Assert.Contains("first_name", names, StringComparer.Ordinal);
300+
Assert.Contains("last_name", names, StringComparer.Ordinal);
301+
302+
// hidden field
303+
Assert.Contains("user_id", names, StringComparer.Ordinal);
304+
}
228305
}
229306
}

0 commit comments

Comments
 (0)