Index: Externals/Eagle/lib/Eagle1.0/vendor.eagle ================================================================== --- Externals/Eagle/lib/Eagle1.0/vendor.eagle +++ Externals/Eagle/lib/Eagle1.0/vendor.eagle @@ -21,10 +21,13 @@ # initialization and/or customizations in here. Additionally, this file # may contain per-interpreter customizations required when porting to # new platforms, operating systems, etc. # +############################################################################### +############################## BEGIN VENDOR CODE ############################## +############################################################################### # # NOTE: Use our own namespace here because even though we do not directly # support namespaces ourselves, we do not want to pollute the global # namespace if this script actually ends up being evaluated in Tcl. # @@ -76,11 +79,21 @@ puts -nonewline $channel [appendArgs \ "Found vendor-specific test package directory \"" $dir \ "\", adding...\n"] } + # + # NOTE: Append the directory to the necessary environment variable + # so that it will get picked up when Eagle actually rebuilds + # the auto-path list (below). + # lappend ::env(EAGLELIBPATH) $dir + + # + # NOTE: Force Eagle to rebuild the auto-path list for the current + # interpreter right now. + # object invoke Utility RefreshAutoPathList } # # NOTE: We are done, return success. @@ -105,9 +118,35 @@ # # NOTE: Directory not found, return failure. # return false } + + proc checkForTestOverrides { channel varNames quiet } { + set result 0 + + foreach varName $varNames { + if {[uplevel 1 [list info exists $varName]]} then { + incr result + + if {!$quiet} then { + puts -nonewline $channel [appendArgs \ + "Found vendor-specific test override \"" $varName "\".\n"] + } + } + } + + return $result + } + + checkForTestOverrides stdout \ + [list binary_directory build_base_directory build_directory \ + common_directory datetime_format test_configuration \ + test_year] false addTestSuiteToAutoPath stdout false } } + +############################################################################### +############################### END VENDOR CODE ############################### +############################################################################### Index: System.Data.SQLite/SQLiteDataReader.cs ================================================================== --- System.Data.SQLite/SQLiteDataReader.cs +++ System.Data.SQLite/SQLiteDataReader.cs @@ -5,11 +5,12 @@ * Released to the public domain, use at your own risk! ********************************************************/ namespace System.Data.SQLite { - using System; + using System; + using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; /// @@ -583,15 +584,143 @@ /// /// Returns a DataTable containing the schema information for the active SELECT statement being processed. public override DataTable GetSchemaTable() { return GetSchemaTable(true, false); + } + + private class ColumnParent : IEqualityComparer + { + public string DatabaseName; + public string TableName; + public string ColumnName; + + public ColumnParent() + { + // do nothing. + } + + public ColumnParent( + string databaseName, + string tableName, + string columnName + ) + : this() + { + this.DatabaseName = databaseName; + this.TableName = tableName; + this.ColumnName = columnName; + } + + #region IEqualityComparer Members + public bool Equals(ColumnParent x, ColumnParent y) + { + if ((x == null) && (y == null)) + { + return true; + } + else if ((x == null) || (y == null)) + { + return false; + } + else + { + if (!String.Equals(x.DatabaseName, y.DatabaseName, + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!String.Equals(x.TableName, y.TableName, + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!String.Equals(x.ColumnName, y.ColumnName, + StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + } + + public int GetHashCode(ColumnParent obj) + { + int result = 0; + + if ((obj != null) && (obj.DatabaseName != null)) + result ^= obj.DatabaseName.GetHashCode(); + + if ((obj != null) && (obj.TableName != null)) + result ^= obj.TableName.GetHashCode(); + + if ((obj != null) && (obj.ColumnName != null)) + result ^= obj.ColumnName.GetHashCode(); + + return result; + } + #endregion + } + + private static void GetStatementColumnParents( + SQLiteBase sql, + SQLiteStatement stmt, + int fieldCount, + ref Dictionary> parentToColumns, + ref Dictionary columnToParent + ) + { + if (parentToColumns == null) + parentToColumns = new Dictionary>( + new ColumnParent()); + + if (columnToParent == null) + columnToParent = new Dictionary(); + + for (int n = 0; n < fieldCount; n++) + { + string databaseName = sql.ColumnDatabaseName(stmt, n); + string tableName = sql.ColumnTableName(stmt, n); + string columnName = sql.ColumnOriginalName(stmt, n); + + ColumnParent key = new ColumnParent(databaseName, tableName, null); + ColumnParent value = new ColumnParent(databaseName, tableName, columnName); + + if (!parentToColumns.ContainsKey(key)) + parentToColumns.Add(key, new List(new int[] { n })); + else + parentToColumns[key].Add(n); + + columnToParent.Add(n, value); + } } internal DataTable GetSchemaTable(bool wantUniqueInfo, bool wantDefaultValue) { - CheckClosed(); + CheckClosed(); + + // + // BUGFIX: We need to quickly scan all the fields in the current + // "result set" to see how many distinct tables are actually + // involved. This information is necessary so that some + // intelligent decisions can be made when constructing the + // metadata below. For example, we need to be very careful + // about flagging a particular column as "unique" just + // because it was in its original underlying database table + // if there are now multiple tables involved in the + // "result set". See ticket [7e3fa93744] for more detailed + // information. + // + Dictionary> parentToColumns = null; + Dictionary columnToParent = null; + + GetStatementColumnParents( + _command.Connection._sql, _activeStatement, _fieldCount, + ref parentToColumns, ref columnToParent); DataTable tbl = new DataTable("SchemaTable"); DataTable tblIndexes = null; DataTable tblIndexColumns; DataRow row; @@ -649,22 +778,22 @@ row[SchemaTableColumn.IsUnique] = false; row[SchemaTableColumn.IsKey] = false; row[SchemaTableOptionalColumn.IsAutoIncrement] = false; row[SchemaTableColumn.DataType] = GetFieldType(n); row[SchemaTableOptionalColumn.IsHidden] = false; - row[SchemaTableColumn.BaseSchemaName] = _baseSchemaName; - - strColumn = _command.Connection._sql.ColumnOriginalName(_activeStatement, n); + row[SchemaTableColumn.BaseSchemaName] = _baseSchemaName; + + strColumn = columnToParent[n].ColumnName; if (String.IsNullOrEmpty(strColumn) == false) row[SchemaTableColumn.BaseColumnName] = strColumn; row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(strColumn); - row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, StringComparison.OrdinalIgnoreCase) != 0); - - temp = _command.Connection._sql.ColumnTableName(_activeStatement, n); - if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp; - - temp = _command.Connection._sql.ColumnDatabaseName(_activeStatement, n); + row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, StringComparison.OrdinalIgnoreCase) != 0); + + temp = columnToParent[n].TableName; + if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp; + + temp = columnToParent[n].DatabaseName; if (String.IsNullOrEmpty(temp) == false) row[SchemaTableOptionalColumn.BaseCatalogName] = temp; string dataType = null; // If we have a table-bound column, extract the extra information from it if (String.IsNullOrEmpty(strColumn) == false) @@ -759,12 +888,18 @@ null }); foreach (DataRow rowColumnIndex in tblIndexColumns.Rows) { if (String.Compare((string)rowColumnIndex["COLUMN_NAME"], strColumn, StringComparison.OrdinalIgnoreCase) == 0) - { - if (tblIndexColumns.Rows.Count == 1 && (bool)row[SchemaTableColumn.AllowDBNull] == false) + { + // + // BUGFIX: Make sure that we only flag this column as "unique" + // if we are not processing of some kind of multi-table + // construct (i.e. a join) because in that case we must + // allow duplicate values (refer to ticket [7e3fa93744]). + // + if (parentToColumns.Count == 1 && tblIndexColumns.Rows.Count == 1 && (bool)row[SchemaTableColumn.AllowDBNull] == false) row[SchemaTableColumn.IsUnique] = rowIndexes["UNIQUE"]; // If its an integer primary key and the only primary key in the table, then its a rowid alias and is autoincrement // NOTE: Currently commented out because this is not always the desired behavior. For example, a 1:1 relationship with // another table, where the other table is autoincrement, but this one is not, and uses the rowid from the other. ADDED Tests/tkt-7e3fa93744.eagle Index: Tests/tkt-7e3fa93744.eagle ================================================================== --- /dev/null +++ Tests/tkt-7e3fa93744.eagle @@ -0,0 +1,139 @@ +############################################################################### +# +# tkt-7e3fa93744.eagle -- +# +# Written by Joe Mistachkin. +# Released to the public domain, use at your own risk! +# +############################################################################### + +package require Eagle +package require EagleLibrary +package require EagleTest + +runTestPrologue + +############################################################################### + +package require System.Data.SQLite.Test +runSQLiteTestPrologue + +############################################################################### + +runTest {test tkt-7e3fa93744-1.1 {composite primary key, baseline} -setup { + setupDb [set fileName tkt-7e3fa93744-1.1.db] +} -body { + set sql { + CREATE TABLE t1 ( + id1 INTEGER PRIMARY KEY + ); + + CREATE TABLE t2 ( + id1 INTEGER NOT NULL, + id2 INTEGER NOT NULL, + PRIMARY KEY (id1, id2) + ); + + INSERT INTO t1 (id1) VALUES (1); + INSERT INTO t1 (id1) VALUES (2); + + INSERT INTO t2 (id1, id2) VALUES (1, 1); + INSERT INTO t2 (id1, id2) VALUES (1, 2); + INSERT INTO t2 (id1, id2) VALUES (2, 1); + INSERT INTO t2 (id1, id2) VALUES (2, 2); + + SELECT t1.id1, t2.id1, t2.id2 + FROM t1, t2 + ORDER BY t1.id1, t2.id1, t2.id2; + } + + sql execute -execute reader -format list $db $sql +} -cleanup { + cleanupDb $fileName + + unset -nocomplain sql db fileName +} -constraints \ +{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \ +{1 1 1 1 1 2 1 2 1 1 2 2 2 1 1 2 1 2 2 2 1 2 2 2}} + +############################################################################### + +runTest {test tkt-7e3fa93744-1.2 {composite primary key, DataTable} -setup { + setupDb [set fileName tkt-7e3fa93744-1.2.db] +} -body { + set id [object invoke Interpreter.GetActive NextId] + set dataSource [file join [getTemporaryPath] $fileName] + + set sql { \ + CREATE TABLE t1 ( \ + id1 INTEGER PRIMARY KEY NOT NULL \ + ); \ + CREATE TABLE t2 ( \ + id1 INTEGER NOT NULL, \ + id2 INTEGER NOT NULL, \ + PRIMARY KEY (id1, id2) \ + ); \ + INSERT INTO t1 (id1) VALUES (1); \ + INSERT INTO t1 (id1) VALUES (2); \ + INSERT INTO t2 (id1, id2) VALUES (1, 1); \ + INSERT INTO t2 (id1, id2) VALUES (1, 2); \ + INSERT INTO t2 (id1, id2) VALUES (2, 1); \ + INSERT INTO t2 (id1, id2) VALUES (2, 2); \ + SELECT t1.id1, t2.id1, t2.id2 \ + FROM t1, t2 \ + ORDER BY t1.id1, t2.id1, t2.id2; \ + } + + unset -nocomplain results errors + + set code [compileCSharpWith [subst { + using System; + using System.Data; + using System.Data.SQLite; + + namespace _Dynamic${id} + { + public class Test${id} + { + public static int Main() + { + using (SQLiteConnection connection = new SQLiteConnection( + "Data Source=${dataSource};")) + { + connection.Open(); + + using (SQLiteCommand command = connection.CreateCommand()) + { + command.CommandText = "${sql}"; + + using (SQLiteDataReader dataReader = command.ExecuteReader()) + { + DataTable dataTable = new DataTable(); + dataTable.Load(dataReader); + + return dataTable.Rows.Count; + } + } + } + } + } + } + }] results errors System.Data.SQLite.dll] + + list $code $results \ + [expr {[info exists errors] ? $errors : ""}] \ + [expr {$code eq "Ok" ? [catch { + object invoke _Dynamic${id}.Test${id} Main + } result] : [set result ""]}] $result +} -cleanup { + cleanupDb $fileName + + unset -nocomplain result code results errors sql dataSource id db fileName +} -constraints \ +{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -match \ +regexp -result {^Ok System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 8$}} + +############################################################################### + +runSQLiteTestEpilogue +runTestEpilogue