1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.IO; 6 using System.Collections; 7 using System.Collections.Generic; 8 using System.Text; 9 using System.Diagnostics; 10 11 namespace System.Data.SqlClient.ManualTesting.Tests 12 { 13 /// <summary> 14 /// represents table with random column types and values in it 15 /// </summary> 16 public class SqlRandomTable 17 { 18 // "Row-Overflow Data Exceeding 8 KB" 19 private const int MaxBytesPerRow = 8060; 20 // however, with sparse columns the limit drops to 8018 21 private const int MaxBytesPerRowWithSparse = 8018; 22 23 // SQL Server uses 6 bytes per-row to store row info (null-bitmap overhead is calculated based on column size) 24 private const int ConstantOverhead = 6; 25 26 // SQL does not allow table creation with more than 1024 non-sparse columns 27 private const int MaxNonSparseColumns = 1024; 28 29 // cannot send more than 2100 parameters in one command 30 private const int MaxParameterCount = 2100; 31 32 /// <summary> 33 /// column types 34 /// </summary> 35 private readonly SqlRandomTableColumn[] _columns; 36 private readonly string[] _columnNames; 37 38 public readonly IList<SqlRandomTableColumn> Columns; 39 public readonly IList<string> ColumnNames; 40 public readonly int? PrimaryKeyColumnIndex; 41 public readonly bool HasSparseColumns; 42 public readonly double NonSparseValuesTotalSize; 43 44 /// <summary> 45 /// maximum size of the row allowed for this column (depends if it has sparse columns or not) 46 /// </summary> 47 public int MaxRowSize 48 { 49 get 50 { 51 if (HasSparseColumns) 52 return MaxBytesPerRowWithSparse; 53 else 54 return MaxBytesPerRow; 55 } 56 } 57 58 /// <summary> 59 /// rows and their values 60 /// </summary> 61 private readonly List<object[]> _rows; 62 63 public IList<object> this[int row] 64 { 65 get 66 { 67 return new List<object>(_rows[row]).AsReadOnly(); 68 } 69 } 70 SqlRandomTable(SqlRandomTableColumn[] columns, int? primaryKeyColumnIndex = null, int estimatedRowCount = 0)71 public SqlRandomTable(SqlRandomTableColumn[] columns, int? primaryKeyColumnIndex = null, int estimatedRowCount = 0) 72 { 73 if (columns == null || columns.Length == 0) 74 throw new ArgumentException("non-empty type array is required"); 75 if (estimatedRowCount < 0) 76 throw new ArgumentOutOfRangeException("non-negative row count is required, use 0 for default"); 77 if (primaryKeyColumnIndex.HasValue && (primaryKeyColumnIndex.Value < 0 || primaryKeyColumnIndex.Value >= columns.Length)) 78 throw new ArgumentOutOfRangeException("primaryColumnIndex"); 79 80 PrimaryKeyColumnIndex = primaryKeyColumnIndex; 81 _columns = (SqlRandomTableColumn[])columns.Clone(); 82 _columnNames = new string[columns.Length]; 83 if (estimatedRowCount == 0) 84 _rows = new List<object[]>(); 85 else 86 _rows = new List<object[]>(estimatedRowCount); 87 88 Columns = new List<SqlRandomTableColumn>(_columns).AsReadOnly(); 89 ColumnNames = new List<string>(_columnNames).AsReadOnly(); 90 bool hasSparse = false; 91 double totalNonSparse = 0; 92 foreach (var c in _columns) 93 { 94 if (c.IsSparse) 95 { 96 hasSparse = true; 97 } 98 else 99 { 100 totalNonSparse += c.GetInRowSize(null); // for non-sparse columns size does not depend on the value 101 } 102 } 103 HasSparseColumns = hasSparse; 104 NonSparseValuesTotalSize = totalNonSparse; 105 } 106 GetColumnName(int c)107 public string GetColumnName(int c) 108 { 109 if (_columnNames[c] == null) 110 { 111 AutoGenerateColumnNames(); 112 } 113 114 return _columnNames[c]; 115 } 116 GetColumnTSqlType(int c)117 public string GetColumnTSqlType(int c) 118 { 119 return _columns[c].GetTSqlTypeDefinition(); 120 } 121 122 /// <summary> 123 /// adds new row with random values, each column has 50% chance to have null value 124 /// </summary> AddRow(SqlRandomizer rand)125 public void AddRow(SqlRandomizer rand) 126 { 127 BitArray nullBitmap = rand.NextBitmap(_columns.Length); 128 AddRow(rand, nullBitmap); 129 } 130 IsPrimaryKey(int c)131 private bool IsPrimaryKey(int c) 132 { 133 return PrimaryKeyColumnIndex.HasValue && PrimaryKeyColumnIndex.Value == c; 134 } 135 136 /// <summary> 137 /// adds a new row with random values and specified null bitmap 138 /// </summary> AddRow(SqlRandomizer rand, BitArray nullBitmap)139 public void AddRow(SqlRandomizer rand, BitArray nullBitmap) 140 { 141 object[] row = new object[_columns.Length]; 142 143 // non-sparse columns will always take fixed size, must add them now 144 double rowSize = NonSparseValuesTotalSize; 145 double maxRowSize = MaxRowSize; 146 147 if (PrimaryKeyColumnIndex.HasValue) 148 { 149 // make sure pkey is set first 150 // ignore the null bitmap in this case 151 object pkeyValue = _rows.Count; 152 row[PrimaryKeyColumnIndex.Value] = pkeyValue; 153 } 154 155 for (int c = 0; c < row.Length; c++) 156 { 157 if (IsPrimaryKey(c)) 158 { 159 // handled above 160 continue; 161 } 162 163 if (SkipOnInsert(c)) 164 { 165 row[c] = null; // this null value should not be used, assert triggered if it is 166 } 167 else if (rowSize >= maxRowSize) 168 { 169 // reached the limit, cannot add more for this row 170 row[c] = DBNull.Value; 171 } 172 else if (nullBitmap[c]) 173 { 174 row[c] = DBNull.Value; 175 } 176 else 177 { 178 object value = _columns[c].CreateRandomValue(rand); 179 if (value == null || value == DBNull.Value) 180 { 181 row[c] = DBNull.Value; 182 } 183 else if (IsSparse(c)) 184 { 185 // check if the value fits 186 double newRowSize = rowSize + _columns[c].GetInRowSize(value); 187 if (newRowSize > maxRowSize) 188 { 189 // cannot fit it, zero this one and try to fit next column 190 row[c] = DBNull.Value; 191 } 192 else 193 { 194 // the value is OK, keep it 195 row[c] = value; 196 rowSize = newRowSize; 197 } 198 } 199 else 200 { 201 // non-sparse values are already counted in NonSparseValuesTotalSize 202 row[c] = value; 203 } 204 } 205 } 206 207 _rows.Add(row); 208 } 209 AddRows(SqlRandomizer rand, int rowCount)210 public void AddRows(SqlRandomizer rand, int rowCount) 211 { 212 for (int i = 0; i < rowCount; i++) 213 AddRow(rand); 214 } 215 GenerateCreateTableTSql(string tableName)216 public string GenerateCreateTableTSql(string tableName) 217 { 218 StringBuilder tsql = new StringBuilder(); 219 220 tsql.AppendFormat("CREATE TABLE {0} (", tableName); 221 222 for (int c = 0; c < _columns.Length; c++) 223 { 224 if (c != 0) 225 tsql.Append(", "); 226 227 tsql.AppendFormat("[{0}] {1}", GetColumnName(c), GetColumnTSqlType(c)); 228 if (IsPrimaryKey(c)) 229 { 230 tsql.Append(" PRIMARY KEY"); 231 } 232 else if (IsSparse(c)) 233 { 234 tsql.Append(" SPARSE NULL"); 235 } 236 else if (IsColumnSet(c)) 237 { 238 tsql.Append(" COLUMN_SET FOR ALL_SPARSE_COLUMNS NULL"); 239 } 240 else 241 { 242 tsql.Append(" NULL"); 243 } 244 } 245 246 tsql.AppendFormat(") ON [PRIMARY]"); 247 248 return tsql.ToString(); 249 } 250 DumpColumnsInfo(TextWriter output)251 public void DumpColumnsInfo(TextWriter output) 252 { 253 for (int i = 0; i < _columnNames.Length - 1; i++) 254 { 255 output.Write(_columnNames[i]); 256 if (_columns[i].StorageSize.HasValue) 257 output.Write(", [StorageSize={0}]", _columns[i].StorageSize.Value); 258 if (_columns[i].Precision.HasValue) 259 output.Write(", [Precision={0}]", _columns[i].Precision.Value); 260 if (_columns[i].Scale.HasValue) 261 output.Write(", [Scale={0}]", _columns[i].Scale.Value); 262 output.WriteLine(); 263 } 264 } 265 DumpRow(TextWriter output, object[] row)266 public void DumpRow(TextWriter output, object[] row) 267 { 268 if (row == null || row.Length != _columns.Length) 269 throw new ArgumentException("Row length does not match the columns"); 270 271 for (int i = 0; i < _columnNames.Length - 1; i++) 272 { 273 object val = row[i]; 274 string type; 275 if (val == null) 276 { 277 val = "<dbnull>"; 278 type = ""; 279 } 280 else 281 { 282 type = val.GetType().Name; 283 if (val is Array) 284 { 285 val = string.Format("[Length={0}]", ((Array)val).Length); 286 } 287 else if (val is string) 288 { 289 val = string.Format("[Length={0}]", ((string)val).Length); 290 } 291 else 292 { 293 val = string.Format("[{0}]", val); 294 } 295 } 296 297 output.WriteLine("[{0}] = {1}", _columnNames[i], val); 298 } 299 } 300 SkipOnInsert(int c)301 private bool SkipOnInsert(int c) 302 { 303 if (_columns[c].Type == SqlDbType.Timestamp) 304 { 305 // cannot insert timestamp 306 return true; 307 } 308 309 if (IsColumnSet(c)) 310 { 311 // skip column set, using sparse columns themselves 312 return true; 313 } 314 315 // OK to insert value 316 return false; 317 } 318 GenerateTableOnServer(SqlConnection con, string tableName)319 public void GenerateTableOnServer(SqlConnection con, string tableName) 320 { 321 // create table 322 SqlCommand cmd = con.CreateCommand(); 323 cmd.CommandType = CommandType.Text; 324 cmd.CommandText = GenerateCreateTableTSql(tableName); 325 326 cmd.ExecuteNonQuery(); 327 328 InsertRows(con, tableName, 0, _rows.Count); 329 } 330 InsertRows(SqlConnection con, string tableName, int rowFrom, int rowToExclusive)331 public void InsertRows(SqlConnection con, string tableName, int rowFrom, int rowToExclusive) 332 { 333 if (con == null || tableName == null) 334 { 335 throw new ArgumentNullException("connection and table name must be valid"); 336 } 337 338 if (rowToExclusive > _rows.Count) 339 { 340 throw new ArgumentOutOfRangeException("rowToExclusive", rowToExclusive, "cannot be greater than the row count"); 341 } 342 343 if (rowFrom < 0 || rowFrom > rowToExclusive) 344 { 345 throw new ArgumentOutOfRangeException("rowFrom", rowFrom, "cannot be less than 0 or greater than rowToExclusive"); 346 } 347 348 SqlCommand cmd = null; 349 SqlParameter[] parameters = null; 350 for (int r = rowFrom; r < rowToExclusive; r++) 351 { 352 InsertRowInternal(con, ref cmd, ref parameters, tableName, r); 353 } 354 } 355 InsertRow(SqlConnection con, string tableName, int row)356 public void InsertRow(SqlConnection con, string tableName, int row) 357 { 358 if (con == null || tableName == null) 359 { 360 throw new ArgumentNullException("connection and table name must be valid"); 361 } 362 363 if (row < 0 || row >= _rows.Count) 364 { 365 throw new ArgumentOutOfRangeException("row", row, "cannot be less than 0 or greater than or equal to row count"); 366 } 367 368 SqlCommand cmd = null; 369 SqlParameter[] parameters = null; 370 InsertRowInternal(con, ref cmd, ref parameters, tableName, row); 371 } 372 InsertRowInternal(SqlConnection con, ref SqlCommand cmd, ref SqlParameter[] parameters, string tableName, int row)373 private void InsertRowInternal(SqlConnection con, ref SqlCommand cmd, ref SqlParameter[] parameters, string tableName, int row) 374 { 375 // cannot use DataTable: it does not handle well char[] values and variant and also does not support sparse/column set ones 376 StringBuilder columnsText = new StringBuilder(); 377 StringBuilder valuesText = new StringBuilder(); 378 379 // create the command and parameters on first call, reuse afterwards (to reduces table creation overhead) 380 if (cmd == null) 381 { 382 cmd = con.CreateCommand(); 383 cmd.CommandType = CommandType.Text; 384 } 385 else 386 { 387 // need to unbind existing parameters and re-add the next set of values 388 cmd.Parameters.Clear(); 389 } 390 391 if (parameters == null) 392 { 393 parameters = new SqlParameter[_columns.Length]; 394 } 395 396 object[] rowValues = _rows[row]; 397 398 // there is a limit of parameters to be sent (2010) 399 for (int ci = 0; ci < _columns.Length; ci++) 400 { 401 if (cmd.Parameters.Count >= MaxParameterCount) 402 { 403 // reached the limit of max parameters, cannot continue 404 // theoretically, we could do INSERT + UPDATE. practically, chances for this to happen are almost none since nulls are skipped 405 rowValues[ci] = DBNull.Value; 406 continue; 407 } 408 409 if (SkipOnInsert(ci)) 410 { 411 // cannot insert timestamp 412 // insert of values into columnset columns are also not supported (use sparse columns themselves) 413 continue; 414 } 415 416 bool isNull = (rowValues[ci] == DBNull.Value || rowValues[ci] == null); 417 418 if (isNull) 419 { 420 // columns such as sparse cannot have DEFAULT constraint, thus it is safe to ignore the value of the column when inserting new row 421 // this also significantly reduces number of columns updated during insert, to prevent "The number of target 422 // columns that are specified in an INSERT, UPDATE, or MERGE statement exceeds the maximum of 4096." 423 continue; 424 } 425 426 SqlParameter p = parameters[ci]; 427 428 // construct column list 429 if (columnsText.Length > 0) 430 { 431 columnsText.Append(", "); 432 valuesText.Append(", "); 433 } 434 435 columnsText.AppendFormat("[{0}]", _columnNames[ci]); 436 437 if (p == null) 438 { 439 p = cmd.CreateParameter(); 440 p.ParameterName = "@p" + ci; 441 p.SqlDbType = _columns[ci].Type; 442 443 parameters[ci] = p; 444 } 445 446 p.Value = rowValues[ci] ?? DBNull.Value; 447 448 cmd.Parameters.Add(p); 449 450 valuesText.Append(p.ParameterName); 451 } 452 453 Debug.Assert(columnsText.Length > 0, "Table that have only TIMESTAMP, ColumnSet or Sparse columns are not allowed - use primary key in this case"); 454 455 cmd.CommandText = string.Format("INSERT INTO {0} ( {1} ) VALUES ( {2} )", tableName, columnsText, valuesText); 456 457 cmd.ExecuteNonQuery(); 458 } 459 460 /// <summary> 461 /// generates SELECT statement; if columnIndices is null the statement will include all the columns 462 /// </summary> GenerateSelectFromTableTSql(string tableName, StringBuilder selectBuilder, int[] columnIndices = null, int indicesOffset = -1, int indicesCount = -1)463 public int GenerateSelectFromTableTSql(string tableName, StringBuilder selectBuilder, int[] columnIndices = null, int indicesOffset = -1, int indicesCount = -1) 464 { 465 if (tableName == null || selectBuilder == null) 466 throw new ArgumentNullException("tableName == null || selectBuilder == null"); 467 468 int maxIndicesLength = (columnIndices == null) ? _columns.Length : columnIndices.Length; 469 if (indicesOffset == -1) 470 { 471 indicesOffset = 0; 472 } 473 else if (indicesOffset < 0 || indicesOffset >= maxIndicesLength) 474 { 475 throw new ArgumentOutOfRangeException("indicesOffset"); 476 } 477 478 if (indicesCount == -1) 479 { 480 indicesCount = maxIndicesLength; 481 } 482 else if (indicesCount < 1 || (indicesCount + indicesOffset) > maxIndicesLength) 483 { 484 // at least one index required 485 throw new ArgumentOutOfRangeException("indicesCount"); 486 } 487 488 double totalRowSize = 0; 489 int countAdded = 0; 490 491 // append the first 492 int columnIndex = (columnIndices == null) ? indicesOffset : columnIndices[indicesOffset]; 493 selectBuilder.AppendFormat("SELECT [{0}]", _columnNames[columnIndex]); 494 totalRowSize += _columns[columnIndex].GetInRowSize(null); 495 countAdded++; 496 497 // append the rest, if any 498 int end = indicesOffset + indicesCount; 499 for (int c = indicesOffset + 1; c < end; c++) 500 { 501 columnIndex = (columnIndices == null) ? c : columnIndices[c]; 502 totalRowSize += _columns[columnIndex].GetInRowSize(null); 503 if (totalRowSize > MaxRowSize) 504 { 505 // overflow - stop now 506 break; 507 } 508 509 selectBuilder.AppendFormat(", [{0}]", _columnNames[columnIndex]); 510 countAdded++; 511 } 512 513 selectBuilder.AppendFormat(" FROM {0}", tableName); 514 515 if (PrimaryKeyColumnIndex.HasValue) 516 selectBuilder.AppendFormat(" ORDER BY [{0}]", _columnNames[PrimaryKeyColumnIndex.Value]); 517 518 return countAdded; 519 } 520 521 #region static helper methods 522 GetRowOverhead(int columnSize)523 private static int GetRowOverhead(int columnSize) 524 { 525 int nullBitmapSize = (columnSize + 7) / 8; 526 return ConstantOverhead + nullBitmapSize; 527 } 528 529 // once we have only this size left on the row, column set column is forced 530 // 40 is an XML and variant size 531 private static readonly int s_columnSetSafetyRange = SqlRandomTypeInfo.XmlRowUsage * 3; 532 533 /// <summary> 534 /// Creates random list of columns from the given source collection. The rules are: 535 /// * table cannot contain more than 1024 non-sparse columns 536 /// * total row size of non-sparse columns should not exceed 8060 or 8018 (with sparse) 537 /// * column set column must be added if number of columns in total exceeds 1024 538 /// </summary> CreateRandTypes(SqlRandomizer rand, SqlRandomTypeInfoCollection sourceCollection, int maxColumnsCount, bool createIdColumn)539 public static SqlRandomTableColumn[] CreateRandTypes(SqlRandomizer rand, SqlRandomTypeInfoCollection sourceCollection, int maxColumnsCount, bool createIdColumn) 540 { 541 var retColumns = new List<SqlRandomTableColumn>(maxColumnsCount); 542 bool hasTimestamp = false; 543 double totalRowSize = 0; 544 int totalRegularColumns = 0; 545 546 bool hasColumnSet = false; 547 bool hasSparseColumns = false; 548 int maxRowSize = MaxBytesPerRow; // set to MaxBytesPerRowWithSparse when sparse column is first added 549 550 int i = 0; 551 if (createIdColumn) 552 { 553 SqlRandomTypeInfo keyType = sourceCollection[SqlDbType.Int]; 554 SqlRandomTableColumn keyColumn = keyType.CreateDefaultColumn(SqlRandomColumnOptions.None); 555 retColumns.Add(keyColumn); 556 totalRowSize += keyType.GetInRowSize(keyColumn, null); 557 i++; 558 totalRegularColumns++; 559 } 560 561 for (; i < maxColumnsCount; i++) 562 { 563 // select column options (sparse/column-set) 564 bool isSparse; // must be set in the if/else flow below 565 bool isColumnSet = false; 566 567 if (totalRegularColumns >= MaxNonSparseColumns) 568 { 569 // reached the limit for regular columns 570 571 if (!hasColumnSet) 572 { 573 // no column-set yet, stop unconditionally 574 // this can happen if large char/binary value brought the row size total to a limit leaving no space for column-set 575 break; 576 } 577 578 // there is a column set, enforce sparse from this point 579 isSparse = true; 580 } 581 else if (i == (MaxNonSparseColumns - 1) && hasSparseColumns && !hasColumnSet) 582 { 583 // we almost reached the limit of regular & sparse columns with, but no column set added 584 // to increase chances for >1024 columns, enforce column set now 585 isColumnSet = true; 586 isSparse = false; 587 } 588 else if (totalRowSize > MaxBytesPerRowWithSparse) 589 { 590 Debug.Assert(totalRowSize <= MaxBytesPerRow, "size over the max limit"); 591 Debug.Assert(!hasSparseColumns, "should not have sparse columns after MaxBytesPerRowWithSparse (check maxRowSize)"); 592 // cannot insert sparse from this point 593 isSparse = false; 594 isColumnSet = false; 595 } 596 else 597 { 598 // check how close we are to the limit of the row size 599 int sparseProbability; 600 if (totalRowSize < 100) 601 { 602 sparseProbability = 2; 603 } 604 else if (totalRowSize < MaxBytesPerRowWithSparse / 2) 605 { 606 sparseProbability = 10; 607 } 608 else if (totalRowSize < (MaxBytesPerRowWithSparse - s_columnSetSafetyRange)) 609 { 610 sparseProbability = 50; 611 } 612 else 613 { 614 // close to the row size limit, special case 615 if (!hasColumnSet) 616 { 617 // if we have not added column set column yet 618 // column-set is a regular column and its size counts towards row size, so time to add it 619 isColumnSet = true; 620 sparseProbability = -1; // not used 621 } 622 else 623 { 624 sparseProbability = 90; 625 } 626 } 627 628 if (!isColumnSet) 629 { 630 isSparse = (rand.Next(100) < sparseProbability); 631 632 if (!isSparse && !hasColumnSet) 633 { 634 // if decided to add regular column, give it a (low) chance to inject a column set at any position 635 isColumnSet = rand.Next(100) < 1; 636 } 637 } 638 else 639 { 640 isSparse = false; 641 } 642 } 643 644 // select the type 645 SqlRandomTypeInfo ti; 646 SqlRandomColumnOptions options = SqlRandomColumnOptions.None; 647 648 if (isSparse) 649 { 650 Debug.Assert(!isColumnSet, "should not have both sparse and column set flags set"); 651 ti = sourceCollection.NextSparse(rand); 652 Debug.Assert(ti.CanBeSparseColumn, "NextSparse must return only types that can be sparse"); 653 options |= SqlRandomColumnOptions.Sparse; 654 } 655 else if (isColumnSet) 656 { 657 Debug.Assert(!hasColumnSet, "there is already a column set, we should not set isColumnSet again above"); 658 ti = sourceCollection[SqlDbType.Xml]; 659 options |= SqlRandomColumnOptions.ColumnSet; 660 } 661 else 662 { 663 // regular column 664 ti = sourceCollection.Next(rand); 665 666 if (ti.Type == SqlDbType.Timestamp) 667 { 668 // while table can contain single timestamp column only, there is no way to insert values into it. 669 // thus, do not allow this 670 if (hasTimestamp || maxColumnsCount == 1) 671 { 672 ti = sourceCollection[SqlDbType.Int]; 673 } 674 else 675 { 676 // table cannot have two timestamp columns 677 hasTimestamp = true; 678 } 679 } 680 } 681 682 SqlRandomTableColumn col = ti.CreateRandomColumn(rand, options); 683 684 if (!isSparse) 685 { 686 double rowSize = ti.GetInRowSize(col, DBNull.Value); 687 int overhead = GetRowOverhead(retColumns.Count + 1); // +1 for this column 688 689 if (totalRowSize + rowSize + overhead > maxRowSize) 690 { 691 // cannot use this column 692 // note that if this column is a column set column 693 continue; 694 } 695 696 totalRowSize += rowSize; 697 totalRegularColumns++; 698 } 699 // else - sparse columns are not counted towards row size when table is created (they are when inserting new row with non-null value in the sparse column)... 700 701 retColumns.Add(col); 702 703 // after adding the column, update the state 704 if (isColumnSet) 705 { 706 hasColumnSet = true; 707 } 708 709 if (isSparse) 710 { 711 hasSparseColumns = true; 712 maxRowSize = MaxBytesPerRowWithSparse; // reduce the max row size 713 } 714 } 715 716 return retColumns.ToArray(); 717 } 718 Create(SqlRandomizer rand, SqlRandomTypeInfoCollection sourceCollection, int maxColumnsCount, int rowCount, bool createPrimaryKeyColumn)719 public static SqlRandomTable Create(SqlRandomizer rand, SqlRandomTypeInfoCollection sourceCollection, int maxColumnsCount, int rowCount, bool createPrimaryKeyColumn) 720 { 721 SqlRandomTableColumn[] testTypes = CreateRandTypes(rand, sourceCollection, maxColumnsCount, createPrimaryKeyColumn); 722 SqlRandomTable table = new SqlRandomTable(testTypes, primaryKeyColumnIndex: createPrimaryKeyColumn ? (Nullable<int>)0 : null, estimatedRowCount: rowCount); 723 table.AddRows(rand, rowCount); 724 return table; 725 } 726 AutoGenerateColumnNames()727 private void AutoGenerateColumnNames() 728 { 729 Dictionary<string, int> nameMap = new Dictionary<string, int>(_columns.Length); 730 for (int c = 0; c < _columns.Length; c++) 731 { 732 if (_columnNames[c] == null) 733 { 734 // pick name that is not in table yet 735 string name = GenerateColumnName(nameMap, c); 736 nameMap[name] = c; 737 _columnNames[c] = name; 738 } 739 else 740 { 741 // check for dups 742 if (nameMap.ContainsKey(_columnNames[c])) 743 { 744 // should not happen now since column names are auto-generated only 745 throw new InvalidOperationException("duplicate column names detected"); 746 } 747 } 748 } 749 } 750 GenerateColumnName(Dictionary<string, int> nameMap, int c)751 private string GenerateColumnName(Dictionary<string, int> nameMap, int c) 752 { 753 string baseName; 754 if (IsPrimaryKey(c)) 755 baseName = "PKEY"; 756 else 757 baseName = string.Format("C{0}_{1}", _columns[c].Type, c); 758 759 string name = baseName; 760 int extraSuffix = 1; 761 while (nameMap.ContainsKey(name)) 762 { 763 name = string.Format("{0}_{1}", baseName, extraSuffix); 764 ++extraSuffix; 765 } 766 return name; 767 } 768 IsSparse(int c)769 private bool IsSparse(int c) 770 { 771 return _columns[c].IsSparse; 772 } 773 IsColumnSet(int c)774 private bool IsColumnSet(int c) 775 { 776 return _columns[c].IsColumnSet; 777 } 778 779 #endregion static helper methods 780 } 781 } 782